Z3库学习笔记
介绍
Z3 在工业应用中实际上常见于软件验证、程序分析等。然而由于功能实在强大,也被用于很多其他领域。CTF 领域来说,能够用约束求解器搞定的问题常见于密码题、二进制逆向、符号执行、Fuzzing 模糊测试等。此外,著名的二进制分析框架 angr 也内置了一个修改版的 Z3。
- 官方使用文档:https://rise4fun.com/z3/tutorialcontent/guide
- z3py 功能手册:https://z3prover.github.io/api/html/namespacez3py.html
- z3py 使用文档:https://ericpony.github.io/z3py-tutorial/guide-examples.htm
- z3 所使用的语法标准:http://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2017-07-18.pdf
安装
这里安装的是 python 库版本,编译好的二进制版本在 Github 下载。
1 | pip install z3-solver |
简单使用
1 | from z3 import * |
进阶使用
设置变量
批量设置变量见[补充](# 补充)
Int - 整数型
1 | # 声明单个变量 |
|
运算需要初始化为Int
变量
Real - 实数型
1 | # 声明单个变量 |
BitVec - 向量(位运算)
1 | # 声明单个 16 位的变量 |
只有 BitVec 变量可以进行异或
1
solver.add(BitVec('x',8)^BitVec('y',8)==5)
BitVec 变量值之间可进行
>
或<
或=
或>=
或<=
的比较1
2
3
4BitVec('a',8)>=BitVec('b',8)
BitVec('a',8)<=BitVec('b',8)
BitVec('a',8)<=9
BitVec('a',8)==9BitVecVal 值之间不能进行
>
或<
比较,只能转换成 python 认识的类型才可以比较1
2
3
4if 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()
对象,方便我们添加更多的约束条件。
创建约束求解器:
1 | solver = Solver() |
添加约束条件
一行一个约束条件,这里的约束条件就是方程等式:
1 | solver.add(x**2+y**2==74) |
z3 中不允许列表与列表之间添加==
约束条件:
1 | from z3 import * |
判断是否有解
1 | if solver.check() == sat: |
求解并输出
1 | ans = solver.model() |
补充
限制结果为可见字符
通常如果是做题的话,解密出来很可能是 flag ,也就是 ascii 码,所以为了进一步约束范围可以给每一个变量都加上额外的一条约束,约束其结果只能在可见 ascii 码范围以内:
1 | solver.add(x < 127) |
快速添加变量
添加 50 个 Int 变量 s :
1 | s=[Int('s%d' % i) for i in range(50)] |
添加 50 个 Real 变量 s :
1 | s=[Real('s%d' % i) for i in range(50)] |
添加 50 个 16 位 BitVec 变量 s :
1 | s=[BitVec ('s%d' % i,16) for i in range(50)] |
在约束条件中用下标索引使用:
1 | solver.add(s[18] * s[8] == 5) |
将结果按顺序打印出来:
这是使用列表管理变量的好处,如果不使用列表 print(answer) 输出的结果是无序的。
1 | answer=solver.model() |
练习例题
2020 羊城杯 login
题目前面还有一步逆向 pyinstaller 打包的 exe 文件,这里不赘述直接给出源码:
1 | import sys |
分析之后确定需要先求解出 a1~a14 的值,然后再经过一次异或获得 flag 。
这里我们手动添加多个变量,因为源码中的方式形式为 ax 。如果我们用列表管理变量,方程需要手动修改,消耗更多时间得不偿失。z3 脚本:
1 | from z3 import * |
这里我们需要将有移位运算的那一条方程注释掉,因为 Int 没有这种运算方法。然后我们知道 a1~a14 是两两整数异或而来,所以加上约束大于等于 0 ,否则由于缺少一条方程解出来的值含有负数。
如果不想注释那条方程,完全使用全部方程,那么就将变量定义为:BitVec('an', 16)
,那么就能够使用移位运算。
然后就是还原异或加密:
1 | a1 = 119 |
2020 CISCN z3
用 IDA 分析题目得知,程序将输入值经过运算后与 Dst 的密文对比,也就是知道解,求出未知数。
Dst 定义是 int 型(8 字节),将密文提取出来:
1 | de = [0x4F17,0x9CF6,0x8DDB,0x8EA6,0x6929,0x9911,0x40A2,0x2F3E,0x62B6,0x4B82,0x486C,0x4002,0x52D7,0x2DEF,0x28DC,0x640D,0x528F,0x613B,0x4781,0x6B17,0x3237,0x2A93,0x615F,0x50BE,0x598E,0x4656,0x5B31,0x313A,0x3010,0x67FE,0x4D5F,0x58DB,0x3799,0x60A0,0x2750,0x3759,0x8953,0x7122,0x81F9,0x5524,0x8971,0x3A1D] |
这里还是用手动申请变量,因为避免修改方程表达式。这道例题可以用 Int 也可以用 BitVec ,这里就用 BitVec
1 | from z3 import * |
极客大挑战 REConvolution
这条题目演示用批量申请堆方法
题目关键函数:
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
加密过程并不是前两题的方程了,而是循环异或,没有修改方程变量名的问题,所以我们可以用 for 循环申请变量。
1 | from z3 import * |
数独
z3 还能处理数独问题,下面是官方 demo :
1 | #!/usr/bin/env python |