z3学习笔记

z3 solver学习笔记

1.0 安装

Z3由微软开发的一套约束求解器,你可以简单的理解它是解方程的神器。 在CTF中有的题目可能会遇到给你一堆条件,但是写不出逆向脚本,或者是使用爆破手段获取flag的时间过长,此时z3的作用便发挥了出来。

由于在上一篇 angr学习笔记中已经了解了虚拟环境 virtualenvwrapper 的用法,我们直接mkvirtualenv --python=$(which python3) z3 && pip install z3-solver即可隔离出一个z3环境,避免与angr冲突。

2.0 示例

2.1最简单的例子

假如我有一个方程

\[x+y=4 \]

用z3求解的脚本

from z3 import *
x = Int('x')
y = Int('y')
solve(x + y == 4)

上例中,x是脚本中的变量,Int('x')是在输出时对应变量x的名称。如果运行以上脚本,得到的答案为[x = 0, y = 1],如果我们这么写

from z3 import *
x = Int('flag[0]')
y = Int('flag[1]')
solve(x + y == 4)

那么输出就是[ flag[0] = 0, flag[1] = 4]

当然这是一个不定方程,如果要得到x = 3的解,我们就要增加约束,比如

from z3 import *
x = Int('x')
y = Int('y')
solve(x + y == 4)
solve(x == 3)

这样输出就是[x = 3, y = 1]

2.2变量声明与约束器solver的建立

可以使用Int,Real,BitVec等声明一个整数或实数变量,也可以申明一个变量数组,如

knownarr = [IntVal(i) for i in encrypted]
message = [Int('flag%d' % i) for i in range(len(encrypted)-1)]
first = IntVector(‘a’, 4)

后面都可以通过arrayname[id]进行访问

3.0 一道例题

做hgame遇到一道矩阵乘法的题,刚好看到一篇博客有类似的题,就拿来做例题吧,等比赛结束了把赛题补上。

反编译后得到的源代码:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp+14h] [ebp-Ch]
  int k; // [esp+18h] [ebp-8h]
  int i; // [esp+1Ch] [ebp-4h]
  int j; // [esp+1Ch] [ebp-4h]

  __main();
  v4 = 0;
  gets(flag);
  for ( i = 0; i <= 35; ++i )
  {
    if ( !flag[i] )
    {
      flag[i] = 1;//flag长度并没有36位,为了凑够36位在flag后面补了数字1
      ++v4;
    }
  }
  if ( v4 != 9 )//可以看出补了9个数字1
    exit(0);
  convert(a);//把flag赋值给a,a是一个6*6的矩阵
  Transposition(a);//把a的转置矩阵赋值给b
  Multi(a, b);//把a * b的结果赋值给c
  for ( j = 0; j <= 5; ++j )
  {
    for ( k = 0; k <= 5; ++k )
    {
      if ( c[j][k] != d[j][k] )//比较c和d
        exit(0);
    }
  }
  printf("congratulations!you have gottern the flag!");
  return 0;
}

如果爆破的话,复杂度极高,可能要跑很长时间,那么我们采用z3求解器该怎么做呢?


from z3 import *
import time
t1 = time.time()
#创建一个解决方案实例
solver = Solver()
#flag长度先设置为36,包括尾部的9个1
flag = [Int('flag%d'%i) for i in range(36)]
#保存flag的矩阵
a = [i for i in flag]
#保存flag的转置矩阵
b = [i for i in range(36)]
#保存a*b的矩阵
c = [0 for i in range(36)]
#堆中正确flag的运算结果
d = [0x12027,0x0F296,0x0BF0E,0x0D84C,0x91D8,0x297,
	0x0F296,0x0D830,0x0A326,0x0B010,0x7627,0x230,
	0x0BF0E,0x0A326,0x8FEB,0x879D,0x70C3,0x1BD,
	0x0D84C,0x0B010,0x879D,0x0B00D,0x6E4F,0x1F7,
	0x91D8,0x7627,0x70C3,0x6E4F,0x9BDC,0x15C,
	0x297,0x230,0x1BD,0x1F7,0x15C,0x6]
#获得a的转置矩阵
for i in range(6):
	for j in range(6):
		b[i+6*j] = a[6*i+j]
#运算a*b
for i in range(6):
	for j in range(6):
		for k in range(6):
			c[j+6*i] = c[j+6*i] + a[6*i+k]*b[6*k+j]
		#添加约束,正确flag的运算结果
		solver.add(simplify(c[j+6*i]) == d[j+6*i])
#添加约束,除了尾部,flag的字符一定在可见字符范围内
for i in range(6,36-10):
	solver.add(flag[i]>=32)
	solver.add(flag[i]<=127)
#添加约束,由于flag有格式,前6位一定为whctf{
for i in range(6):
	solver.add(flag[i] == ord('whctf{'[i]))
#添加约束,flag的尾部为9个1
for i in range(36-9,36):
	solver.add(flag[i] == 0x1)
#添加约束,flag的最后一个肯定是}
solver.add(flag[-10] == ord('}'))
#这里一定要有,不check的话会报错
if solver.check() == sat:
	m = solver.model()
	s = []
	#获得结果
	for i in range(36):
		s.append(m[flag[i]].as_long())
	#输出flag
	print(bytes(s))
else:
	print('error')

这个脚本还有点问题,但是结构是对的,赛后和hagme题解交叉引用一下(doge)

posted @ 2022-01-25 23:10  EPs1l0h  阅读(200)  评论(0编辑  收藏  举报