关于Z3求解器的学习(转载)

以下全部摘自别人,这个工具我懒得看官方文档,等需要时来看吧.


Z3 简介

Z3 是一个微软出品的开源约束求解器,能够解决很多种情况下的给定部分约束条件寻求一组满足条件的解的问题(可以简单理解为解方程的感觉,虽然这么比喻其实还差距甚远,请勿吐槽),功能强大且易于使用,本文以近期的 CTF 题为实例,向尚未接触过约束求解器的小伙伴们介绍 Z3 在 CTF 解题中的应用。

Z3 约束求解器是针对 Satisfiability modulo theories Problem 的一种通用求解器。所谓 SMT 问题,在 Z3 环境下是指关于算术、位运算、数组等背景理论的一阶逻辑组合决定性问题。虽然 Z3 功能强大,但是从理论上来说,大部分 SMT 问题的时间复杂度都过高,根本不可能在有限时间内解决。所以千万不要把 Z3 想象得过于万能。

Z3 在工业应用中实际上常见于软件验证、程序分析等。然而由于功能实在强大,也被用于很多其他领域。CTF 领域来说,能够用约束求解器搞定的问题常见于密码题、二进制逆向、符号执行、Fuzzing 模糊测试等。此外,著名的二进制分析框架 angr 也内置了一个修改版的 Z3。


以下来自z3 - CTF Wiki

安装

Z3 提供了多种语言的接口,方便起见我们使用 Python 版本,我们可以直接通过 pip 进行安装(注意这里应当为 z3-solver 而非 z3):

$ pip3 install z3-solver

基本用法

本节仅介绍 z3 最基本的用法,更多高级用法参见官方文档

变量表示

一阶命题逻辑公式由项(变量或常量)与扩展布尔结构组成,在 z3 当中我们可以通过如下方式创建变量实例:

  • 整型(integer,长度不限)
>>> import z3
>>> x = z3.Int(name = 'x') # x is an integer
  • 实数类型(real number,长度不限)
>>> y = z3.Real(name = 'y') # y is a real number
  • 位向量(bit vector,长度需在创建时指定
>>> z = z3.BitVec(name = 'z', bv = 32) # z is a 32-bit vector
  • 布尔类型(bool)
>>> p = z3.Bool(name = 'p')

整型与实数类型变量之间可以互相进行转换:

>>> z3.ToReal(x)
ToReal(x)
>>> z3.ToInt(y)
ToInt(y)

常量表示

除了 Python 原有的常量数据类型外,我们也可以使用 z3 自带的常量类型参与运算:

>>> z3.IntVal(val = 114514) # integer
114514
>>> z3.RealVal(val = 1919810) # real number
1919810
>>> z3.BitVecVal(val = 1145141919810, bv = 32) # bit vector,自动截断
2680619074
>>> z3.BitVecVal(val = 1145141919810, bv = 64) # bit vector
1145141919810

求解器

在使用 z3 进行约束求解之前我们首先需要获得一个 求解器 类实例,本质上其实就是一组约束的集合

>>> s = z3.Solver()

添加约束

我们可以通过求解器的 add() 方法为指定求解器添加约束条件,约束条件可以直接通过 z3 变量组成的式子进行表示:

>>> s.add(x * 5 == 10)
>>> s.add(y * 1/2 == x)

对于布尔类型的式子而言,我们可以使用 z3 内置的 And()Or()Not()Implies() 等方法进行布尔逻辑运算:

>>> s.add(z3.Implies(p, q))
>>> s.add(r == z3.Not(q))
>>> s.add(z3.Or(z3.Not(p), r))

约束求解

当我们向求解器中添加约束条件之后,我们可以使用 check() 方法检查约束是否是可满足的(satisfiable,即 z3 是否能够帮我们找到一组解):

  • z3.sat:约束可以被满足
  • z3.unsat:约束无法被满足
>>> s.check()
sat

若约束可以被满足,则我们可以通过 model() 方法获取到一组解:

>>> s.model()
[q = True, p = False, x = 2, y = 4, r = False]

对于约束条件比较少的情况,我们也可以无需创建求解器,直接通过 solve() 方法进行求解:

>>> z3.solve(z3.Implies(p, q), r == z3.Not(q), z3.Or(z3.Not(p), r))
[q = True, p = False, r = False]

以下来自https://www.cnblogs.com/myx-myx/articles/17009330.html

Z3基础学习(一) 从例子入门

打了星号的标题表示没有掌握

一.解不等式

例1.

输入:

from z3 import *
x = Int('x')
y = Int('y')
solve(x > 2, y < 10, x + 2*y == 7)

输出:

[y = 0, x = 7]

函数Int('x')创建了一个名为x的变量。函数solve解决了一个约束系统。上面的例子用到了两个变量x和y,以及三个约束条件(x>2)、(y<10)、(x+2y7)。Z3向python使用=进行赋值。运算符<,<=,>,>=,和!=用于比较。上面的例子中,表达式x+2y==7是一个Z3约束。Z3可以解决和紧缩公式。

二.简化器

使用simplify将表达式进行化简

例1.

输入:

from z3 import *
x=Int('x')
y=Int('y')
print (simplify(x + y + 2*x + 3))
print (simplify(x < y + x + 2))
print (simplify(And(x + 1 >= 3, x**2 + x**2 + y**2 + 2 >= 5)))

输出:

3 + 3*x + y
Not(y <= -2)
And(x >= 2, 2*x**2 + y**2 >= 3)

三.表达式分析

Z3提供遍历表达式的函数。

输入

from z3 import *
x = Int('x')
y = Int('y')
n = x + y >= 3
print ("num args: ", n.num_args())
print ("children: ", n.children())
print ("1st child:", n.arg(0))
print ("2nd child:", n.arg(1))
print ("operator: ", n.decl())
print ("op name:  ", n.decl().name())

输出

num args:  2
children:  [x + y, 3]
1st child: x + y
2nd child: 3
operator:  >=
op name:   >=

四.数学运算

Z3提供了所有基本的数学运算。 Z3使用Python语言的相同运算符优先级。 像Python一样,**是幂运算。 Z3可以求解非线性多项式约束。

通常,是逻辑与,是逻辑或

例1.

输入

from z3 import *
x = Real('x')
y = Real('y')
solve(x**2 + y**2 > 3, x**3 + y < 5)

输出

[y = 2, x = 1/8]

其中,Real('x')创建实际变量x。 Z3可以表示任意大的整数,有理数(如上例)和无理代数。 一个无理数代数是具有整数系数的多项式的根。 在内部,Z3精确地代表了所有这些数字。 无理数以十进制表示形式显示,以便读取结果。

五.精度设置

set_option用于配置Z3环境。 它用于设置全局配置选项,如结果如何显示。 选项set_option(precision = 30)设置显示结果时使用的小数位数。 这个 标记在1.2599210498? 中表示输出被截断。

Real类型的数据求解默认为分数形式,如果设置rational_to_decimal=True则结果位小数形式,默认显示小数点后10位,10位后用?表示后面还有数值

例1.

输入:

from z3 import *
x = Real('x')
y = Real('y')
solve(x**2 + y**2 == 3, x**3 == 2)

set_option(precision=30)
#precision=30即保留30位小数
print ("Solving, and displaying result with 30 decimal places")
solve(x**2 + y**2 == 3, x**3 == 2)

输出:

[y = -1.1885280594?, x = 1.2599210498?]
Solving, and displaying result with 30 decimal places
[y = -1.188528059421316533710369365015?,
 x = 1.259921049894873164767210607278?]

例2.

以下示例演示了一个常见的错误。?

表达式1/3是一个Python整数,而不是Z3有理数。 该示例还显示了在Z3Py中创建有理数的不同方法。 程序Q(num,den)创建一个Z3有理数,其中num是分子,den是分母。 RealVal(1)创建一个表示数字1的Z3实数。

注意观察对应的输入输出

输入:

from z3 import *
print (1 / 3)
print (RealVal(1)/3)
print (Q(1,3))

x = Real('x')
print (x + 1/3)
print (x + Q(1,3))
print (x + "1/3")
print (x + 0.25)

输出:

0.3333333333333333
1/3
1/3
x + 3333333333333333/10000000000000000
x + 1/3
x + 1/3
x + 1/4

例3.

有理数也可以用十进制表示法显示。

输入:

from z3 import *
x = Real('x')
solve(3*x == 1)

set_option(rational_to_decimal=True)
solve(3*x == 1)

set_option(precision=30)
solve(3*x == 1)

输出:

[x = 1/3]
[x = 0.3333333333?]
[x = 0.333333333333333333333333333333?]

六.不可满足/无解

约束系统也可能没有解决方案。 在这种情况下,我们说这个系统是不可满足的。

输入:

from z3 import *
x = Real('x')
solve(x > 4, x < 0)

输出:

no solution

七.注释

像在Python中一样,注释以开头,并在行尾结束。 Z3Py不支持跨越多行的评论

八.BOOL LOGIC 布尔逻辑*

Z3支持布尔运算符:And, Or, Not, Implies (implication), If (if-then-else)。双蕴含符号用==表示。 以下示例显示如何解决一组简单的布尔约束。

输入:

from z3 import *
p = Bool('p')
q = Bool('q')
r = Bool('r')
solve(Implies(p, q), r == Not(q), Or(Not(p), r))

输出:

[q = True, p = False, r = False]

九.True、False in Python*

Python布尔常量TrueFalse可用于构建Z3布尔表达式。

输入:

from z3 import *
p = Bool('p')
q = Bool('q')
print (And(p, q, True))
print (simplify(And(p, q, True)))
print (simplify(And(p, False)))

输出:

And(p, q, True)
And(p, q)
False

十.多项式与布尔组合

以下示例使用多项式和布尔约束的组合。

输入:

from z3 import *
p = Bool('p')
x = Real('x')
solve(Or(x < 5, x > 10), Or(p, x**2 == 2), Not(p))

输出:

[x = -1.4142135623?, p = False]

因为solve中的三个assert都要满足,所以Not(p)推出p = False, 所以x**2 == 2要成立,所以x = +- sqrt(2)。又因为x > 10不可能,所以就是x < 5,也就是正负根号2都可以,只输出一个解即可,所以输出负根号2.

十一.pop / push 断言堆栈*

输入:

from z3 import *
x = Int('x')
y = Int('y')

s = Solver()
print (s)

s.add(x > 10, y == x + 2)
print (s)
print ("Solving constraints in the solver s ...")
print (s.check())

print ("Create a new scope...")
s.push()
s.add(y < 11)
print (s)
print ("Solving updated set of constraints...")
print ()

print ("Restoring state...")
s.pop()
print (s)
print ("Solving restored set of constraints...")
print (s.check())

输出:

[]
[x > 10, y == x + 2]
Solving constraints in the solver s ...
sat
Create a new scope...
[x > 10, y == x + 2, y < 11]
Solving updated set of constraints...
unsat
Restoring state...
[x > 10, y == x + 2]
Solving restored set of constraints...
sat

可以看到,一开始求解器为空,后来加上两个断言之后,求解器的context就有了那两个断言。check求解器得到结果。sat 意味着满足(satisfied)。接下来创建了一个新的范围,可以看到新增了一个断言,这时候check的结果就是unsat,意味着不可满足(unsatisfied). 再把新增的assert 弹出(pop)之后,可以看到又sat了。

Solver()命令创建一个通用求解器。约束可以使用方法add添加。方法check()解决了断言的约束。如果找到解决方案,结果是sat(满足)。如果不存在解决方案,结果unsat(不可满足)。我们也可以说,所声明的约束系统是不可行的(infeasible)。最后,求解器可能无法解决约束系统并返回unknown(未知)。

在一些应用中,我们想要探索几个共享几个约束的类似问题。我们可以使用pushpop命令来做到这一点。每个求解器维护一堆断言。命令push通过保存当前堆栈大小来创建一个新的作用域。命令pop删除它与匹配推送之间执行的任何断言。检查方法始终对求解器断言堆栈的内容进行操作。

十二.只能求解非线形多项式约束

以下示例显示了Z3无法解决的示例。求解器在这种情况下返回未知数。回想一下,Z3可以求解非线性多项式约束,但2 ** x不是一个多项式。

输入:

from z3 import *
x = Real('x')
s = Solver()
s.add(2**x == 3)
print (s.check())

输出:

unknown

十三.遍历 / 性能统计*

以下示例显示如何遍历断言解析器中的约束,以及如何收集检查方法的性能统计信息。

输入:

from z3 import *
x = Real('x')
y = Real('y')
s = Solver()
s.add(x > 1, y > 1, Or(x + y > 3, x - y < 2))
print ("asserted constraints...")
for c in s.assertions():
    print (c)

print (s.check())
print ("statistics for the last check method...")
print (s.statistics())
# Traversing statistics
#遍历统计
for k, v in s.statistics():
    print ("%s : %s" % (k, v))

输出:

asserted constraints...
x > 1
y > 1
Or(x + y > 3, x - y < 2)
sat
statistics for the last check method...
(:arith-lower         1
 :arith-make-feasible 3
 :arith-max-columns   8
 :arith-max-rows      2
 :arith-upper         3
 :decisions           2
 :final-checks        1
 :max-memory          19.63
 :memory              19.23
 :mk-bool-var         4
 :mk-clause-binary    1
 :num-allocs          433302
 :num-checks          1
 :rlimit-count        355
 :time                0.00)
decisions : 2
final checks : 1
mk clause binary : 1
num checks : 1
mk bool var : 4
arith-lower : 1
arith-upper : 3
arith-make-feasible : 3
arith-max-columns : 8
arith-max-rows : 2
num allocs : 433302
rlimit count : 355
max memory : 19.63
memory : 19.23
time : 0.004

当Z3找到一组已确定约束的解决方案时,check就会返回sat, 我们就可以说Z3满足了这些约束条件,这个解决方案是这组声明约束的modelmodel是使每个断言约束都为真的interpretation。(大致翻译:model 模型, interpretion:实例)

十四.检查模型

以下示例显示了检查模型的基本方法。

输入:

from z3 import *
x, y, z = Reals('x y z')
s = Solver()
s.add(x > 1, y > 1, x + y > 3, z - x < 10)
print (s.check())

m = s.model()
print ("x = %s" % m[x])

print ("traversing model...")
for d in m.decls():
    print ("%s = %s" % (d.name(), m[d]))

输出:

sat
x = 3/2
traversing model...
y = 2
x = 3/2
z = 0

在上面的例子中,函数Reals('x y z')创建变量 x,y和z。 它是以下三句话的缩写:

x = Real('x')
y = Real('y')
z = Real('z')

注意Reals 加了s

表达式m [x]返回模型m中对x的解释。 表达式“%s=%s”%(d.name(),m [d])返回一个字符串,其中第一个%s被替换为d的名字(即d.name()),第二个 %s用文本表示d的解释(即m [d])。 Z3Py在需要时自动将Z3对象转换为文本表示。(这是python的写法)

十五.整数/实数变量

输入:

from z3 import *
x = Real('x')
y = Int('y')
a, b, c = Reals('a b c')
s, r = Ints('s r')
print (x + y + 1 + (a + s))
print (ToReal(y) + c)

输出:

x + ToReal(y) + 1 + a + ToReal(s)
ToReal(y) + c

函数ToReal将整型表达式转换为实型表达式。

Z3Py支持所有基本的算术运算。

十六.算数运算

输入:

from z3 import *
a, b, c = Ints('a b c')
d, e = Reals('d e')
solve(a > b + 2,
      a == 2*c + 10,
      c + b <= 1000,
      d >= e)

输出:

[b = 0, c = 0, e = 0, d = 0, a = 10]

十七.Simplify

simplify命令对Z3表达式应用简单的转换。

输入

from z3 import *
x, y = Reals('x y')
# Put expression in sum-of-monomials form
t = simplify((x + y)**3, som=True)
print (t)
# Use power operator
t = simplify(t, mul_to_power=True)
print (t)

输出:

x*x*x + 3*x*x*y + 3*x*y*y + y*y*y
x**3 + 3*x**2*y + 3*x*y**2 + y**3

十八.help_simplify*

help_simplify() 命令打印所有可用的选项。 Z3Py允许用户以两种风格书写选项。 Z3内部选项名称以:开头,单词之间用 - 分隔。 这些选项可以在Z3Py中使用。 Z3Py还支持类似Python的名称,其中:被压缩并且被_替换。

以下示例演示如何使用这两种样式。

输入:

from z3 import *
x, y = Reals('x y')
# Using Z3 native option names
print (simplify(x == y + 2, ':arith-lhs', True))
# Using Z3Py option names
print (simplify(x == y + 2, arith_lhs=True))

print ("\nAll available options:")
help_simplify()

输出:

x + -1*y == 2
x + -1*y == 2

All available options:
algebraic_number_evaluator (bool) simplify/evaluate expressions containing (algebraic) irrational numbers. (default: true)
arith_ineq_lhs (bool) rewrite inequalities so that right-hand-side is a constant. (default: false)
arith_lhs (bool) all monomials are moved to the left-hand-side, and the right-hand-side is just a constant. (default: false)
bit2bool (bool) try to convert bit-vector terms of size 1 into Boolean terms (default: true)
blast_distinct (bool) expand a distinct predicate into a quadratic number of disequalities (default: false)
blast_distinct_threshold (unsigned int) when blast_distinct is true, only distinct expressions with less than this number of arguments are blasted (default: 4294967295)
blast_eq_value (bool) blast (some) Bit-vector equalities into bits (default: false)
blast_select_store (bool) eagerly replace all (select (store ..) ..) term by an if-then-else term (default: false)
bv_extract_prop (bool) attempt to partially propagate extraction inwards (default: false)
bv_ineq_consistency_test_max (unsigned int) max size of conjunctions on which to perform consistency test based on inequalities on bitvectors. (default: 0)
bv_ite2id (bool) rewrite ite that can be simplified to identity (default: false)
bv_le2extract (bool) disassemble bvule to extract (default: true)
bv_le_extra (bool) additional bu_(u/s)le simplifications (default: false)   
bv_not_simpl (bool) apply simplifications for bvnot (default: false)        
bv_sort_ac (bool) sort the arguments of all AC operators (default: false)   
cache_all (bool) cache all intermediate results. (default: false)
elim_and (bool) conjunctions are rewritten using negation and disjunctions (default: false)
elim_ite (bool) eliminate ite in favor of and/or (default: true)
elim_rem (bool) replace (rem x y) with (ite (>= y 0) (mod x y) (- (mod x y))). (default: false)
elim_sign_ext (bool) expand sign-ext operator using concat and extract (default: true)
elim_to_real (bool) eliminate to_real from arithmetic predicates that contain only integers. (default: false)
eq2ineq (bool) expand equalities into two inequalities (default: false)     
expand_nested_stores (bool) replace nested stores by a lambda expression (default: false)
expand_power (bool) expand (^ t k) into (* t ... t) if  1 < k <= max_degree. (default: false)
expand_select_ite (bool) expand select over ite expressions (default: false)
expand_select_store (bool) conservatively replace a (select (store ...) ...) term by an if-then-else term (default: false)
expand_store_eq (bool) reduce (store ...) = (store ...) with a common base into selects (default: false)
expand_tan (bool) replace (tan x) with (/ (sin x) (cos x)). (default: false)
flat (bool) create nary applications for and,or,+,*,bvadd,bvmul,bvand,bvor,bvxor (default: true)
gcd_rounding (bool) use gcd rounding on integer arithmetic atoms. (default: false)
hi_div0 (bool) use the 'hardware interpretation' for division by zero (for bit-vector terms) (default: true)
hoist_ite (bool) hoist shared summands under ite expressions (default: false)
hoist_mul (bool) hoist multiplication over summation to minimize number of multiplications (default: false)
ignore_patterns_on_ground_qbody (bool) ignores patterns on quantifiers that don't mention their bound variables. (default: true)
ite_extra_rules (bool) extra ite simplifications, these additional simplifications may reduce size locally but increase globally (default: false)       
local_ctx (bool) perform local (i.e., cheap) context simplifications (default: false)
local_ctx_limit (unsigned int) limit for applying local context simplifier (default: 4294967295)
max_degree (unsigned int) max degree of algebraic numbers (and power operators) processed by simplifier. (default: 64)
max_memory (unsigned int) maximum amount of memory in megabytes (default: 4294967295)
max_steps (unsigned int) maximum number of steps (default: 4294967295)      
mul2concat (bool) replace multiplication by a power of two into a concatenation (default: false)
mul_to_power (bool) collpase (* t ... t) into (^ t k), it is ignored if expand_power is true. (default: false)
pull_cheap_ite (bool) pull if-then-else terms when cheap. (default: false)  
push_ite_arith (bool) push if-then-else over arithmetic terms. (default: false)
push_ite_bv (bool) push if-then-else over bit-vector terms. (default: false)
push_to_real (bool) distribute to_real over * and +. (default: true)        
rewrite_patterns (bool) rewrite patterns. (default: false)
som (bool) put polynomials in sum-of-monomials form (default: false)        
som_blowup (unsigned int) maximum increase of monomials generated when putting a polynomial in sum-of-monomials normal form (default: 10)
sort_store (bool) sort nested stores when the indices are known to be different (default: false)
sort_sums (bool) sort the arguments of + application. (default: false)      
split_concat_eq (bool) split equalities of the form (= (concat t1 t2) t3) (default: false)

rith-lhs参数被设置成True(default:False),意味着所有参数都放在左手边,右手边只留下常数constant。

十九.无理数、任意大

Z3Py支持任意大的数字。 以下示例演示如何使用较大的整数,有理数和无理数执行基本算术。 Z3Py仅支持代数无理数。 代数无理数对于呈现多项式约束系统的解是足够的。 Z3Py将始终以十进制符号显示无理数,因为它更便于阅读。 内部表示可以使用sexpr()方法提取。 它以s表达式(Lisp-like)的形式显示数学公式和表达式的Z3内部表示。

输入:

from z3 import *
x, y = Reals('x y')
solve(x + 10000000000000000000000 == y, y > 20000000000000000)

print (Sqrt(2) + Sqrt(3))
print (simplify(Sqrt(2) + Sqrt(3)))
print (simplify(Sqrt(2) + Sqrt(3)).sexpr())
# sexpr() 方法可用于任何 Z3 表达式
print ((x + Sqrt(y) * 2).sexpr())

输出:

[y = 20000000000000001, x = -9999979999999999999999]
2**(1/2) + 3**(1/2)
3.1462643699?
(root-obj (+ (^ x 4) (* (- 10) (^ x 2)) 1) 4)
(+ x (* (^ y (/ 1.0 2.0)) 2.0))

二十.位向量

以下示例演示如何创建位向量变量和常量。

函数BitVec('x',16)在Z3中创建一个位向量变量,名称为x,具有16位。 为了方便起见,可以使用整型常量在Z3Py中创建位向量表达式。 函数BitVecVal(10,32)创建一个大小为32的位向量,其值为10。

输入:

from z3 import *
x = BitVec('x', 16)
y = BitVec('y', 16)
print (x + 2)
# 内部
print ((x + 2).sexpr())

# -1 等于 65535 对应 16-bit 整数 
print (simplify(x + y - 1))

# 创建位向量常量
a = BitVecVal(-1, 16)
b = BitVecVal(65535, 16)
print (simplify(a == b))

a = BitVecVal(-1, 32)
b = BitVecVal(65535, 32)
# -1 对于 32 位整数不等于 65535 
print (simplify(a == b))

输出:

x + 2
(bvadd x #x0002)
65535 + x + y
True
False

与诸如C,C ++,C#,Java等编程语言相比,有符号和无符号的位向量之间没有区别。 相反,Z3提供了算术运算的特殊符号版本,无论位向量是有符号还是无符号都是有区别的。 在Z3Py中,运算符<<=>> =/>>对应于有符号的版本。 相应的,无符号运算符是ULTULEUGTUGEUDivURemLShR

输入:

from z3 import *
# 创建大小为 32 的位向量
x, y = BitVecs('x y', 32)

solve(x + y == 2, x > 0, y > 0)

#  位运算符
# &  按位与
# | 按位异或
# ~ 按位取反
solve(x & y == ~y)

solve(x < 0)

# using unsigned version of < 
solve(ULT(x, 0))

输出:

[y = 1, x = 1]
[y = 4294967295, x = 0]
[x = 4294967295]
no solution

二十一.位移运算

算子>>是算术右移,而<<是左移。 位移符号是左结合的。

输入:

from z3 import *
# 创建大小为 32 的位向量
x, y = BitVecs('x y', 32)

solve(x >> 2 == 3)

solve(x << 2 == 3)

solve(x << 2 == 24)

输出:

[x = 12]
no solution
[x = 6]

二十二.FUNCTIONS 函数

在常见的编程语言中,函数具有副作用,可以抛出异常或永不返回。但在Z3中,函数是没有副作用的,并且是完全的。也就是说,它们是在所有输入值上定义的。比如除法函数。 Z3是基于一阶逻辑的。

给定一个约束,如x + y > 3,我们说x和y是变量。在许多教科书中,x和y被称为未解释的常量。也就是说,它们允许任何与约束x + y> 3一致的解释。

更确切地说,纯粹的一阶逻辑中的函数和常量符号是不解释的(uninterprete)、或自由的(free),这意味着没有附加先验解释。这与属于理论特征的函数形成对比,例如函数+具有固定的标准解释(它将两个数字相加)。不解释的函数和常量能够达到最大程度地灵活;它们允许任何与函数或常数约束相一致的解释。

为了说明未解释的函数和常量,让我们用一个例子来说明。

输入:

from z3 import *
x = Int('x')
y = Int('y')
f = Function('f', IntSort(), IntSort())
solve(f(f(x)) == x, f(x) == y, x != y)

输出:

[x = 0, y = 1, f = [1 -> 0, else -> 1]]

设不解释的整数常量(又名变量)x,y;设f是一个不解释函数,它接受一个类型(又名 sort)整数的参数并生成一个整数值。这个例子说明了如何强制一个解释,其中f再次应用于x导致x,但是f应用于x不同于x。对于 f 的解(Interpretation)应该被解读为:

f(0)= 1,f(1)= 0,并且对于全部不同于0和1的a, f(a)=1。

在Z3中,我们也可以评估模型中的约束系统表达式。 以下示例显示如何使用评估方法。

输入:

from z3 import *
x = Int('x')
y = Int('y')
f = Function('f', IntSort(), IntSort())
s = Solver()
s.add(f(f(x)) == x, f(x) == y, x != y)
print (s.check())
m = s.model()
print ("f(f(x)) =", m.evaluate(f(f(x))))
print ("f(x)    =", m.evaluate(f(x)))

输出:

sat
f(f(x)) = 0
f(x)    = 1

Z3基础学习 (二) 基础语法

一.设置变量

Int - 整数型

# 声明单个变量
x = Int('x')
# 声明多个变量
y,z = Ints('y z')

| 运算需要初始化为Int变量
Real - 实数型

# 声明单个变量
x = Real('x')
# 声明多个变量
y,z = Reals('y z')

BitVec - 向量(位运算)

# 声明单个 16 位的变量
x = BitVec('x',16)
# 声明多个 16 位的变量
y,z = BitVecs('y z',16)

只有 BitVec 变量可以进行异或

solver.add(BitVec('x',8)^BitVec('y',8)==5)
BitVec 变量值之间可进行>或<或=或>=或<=的比较
BitVec('a',8)>=BitVec('b',8)   
BitVec('a',8)<=BitVec('b',8)   
BitVec('a',8)<=9   BitVec('a',8)==9 
BitVecVal 值之间不能进行>或<比较,只能转换成 python 认识的类型才可以比较
if BitVecVal(98,8)>BitVecVal(97,8)#错误,不是python类型  
if BitVecVal(98,8)==98:   
if BitVecVal(98,8).as_long()>97   
if BitVecVal(98,8).as_long()>BitVecVal(97,8).as_long()

变量设置的类型可能会影响到最后求解的结果。可以先 check 一下看看有没有解,然后再判断是否需要切换变量的类型。

二.Solver 对象

实际做题时,约束条件肯定不会想上面例子这么少,所以需要实例化一个 Solver() 对象,方便我们添加更多的约束条件。

创建约束求解器:
solver = Solver()

三.添加约束条件

一行一个约束条件,这里的约束条件就是方程等式:

solver.add(x**2+y**2==74)
solver.add(x**5-y==z)
# [y = -7, x = 5, z = 3132]

z3 中不允许列表与列表之间添加==约束条件:

from z3 import *
prefix=[ord(each) for each in "flag{"]
plain_line=[Int('x%d' % i) for i in range(5)]
s=Solver()
z=0
#s.add(plain_line[z:z+5]==prefix) 列表==列表-->错误
s.add(plain_line[z]==prefix[0])
s.add(plain_line[z+1]==prefix[1])
s.add(plain_line[z+2]==prefix[2])
s.add(plain_line[z+3]==prefix[3])
s.add(plain_line[z+4]==prefix[4])
s.check()
print(s.model())
## 四.判断是否有解
```py
if solver.check() == sat:
    print("solver")
else:
    print("no solver")

五.求解并输出

ans = solver.model()
print(ans)

六.限制结果为可见字符

通常如果是做题的话,解密出来很可能是 flag ,也就是 ascii 码,所以为了进一步约束范围可以给每一个变量都加上额外的一条约束,约束其结果只能在可见 ascii 码范围以内:

solver.add(x < 127)
solver.add(x >= 32)

七.快速添加变量

添加 50 个 Int 变量 s :

s=[Int('s%d' % i) for i in range(50)]

添加 50 个 Real 变量 s :

s=[Real('s%d' % i) for i in range(50)]

添加 50 个 16 位 BitVec 变量 s :

s=[BitVec ('s%d' % i,16) for i in range(50)]

在约束条件中用下标索引使用:

solver.add(s[18] * s[8] == 5)
solver.add(s[4] * s[11] == 0)

将结果按顺序打印出来:

这是使用列表管理变量的好处,如果不使用列表 print(answer) 输出的结果是无序的。

answer=solver.model()
#print(answer)
result="".join([str(answer[each]) for each in s])
print(result)
posted @   T0fV404  阅读(190)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示