Z3是什么?Z3由微软开发的一套约束求解器,你可以简单的理解它是解方程的神器。在CTF题目中,我们经常遇到一些给定的条件,或是算法难以逆向、或是涉及到未知的数学技巧又或是爆破时间过长,而在我们学会使用z3后,一类问题便迎刃而解了。想了解更多关于z3知识的,这里有篇专栏:点我
一个简单的例子给大家介绍一下z3如何使用:
>>> from z3 import *
>>> x = Int('x')
>>> y = Int('y')
>>> solve(x+y==4)
[y = 0, x = 4]
它为我们提供了一个关于x+y==4的解,可是如果我们想要x=3呢?
>>> solve(x==3,x+y==4)
[y = 1, x = 3]
当然了,z3能做的肯定不止这么简单的运算,例如:
>>> from z3 import *
>>> x = Real('x')
>>> y = Real('y')
>>> solve(x**2 + y**2 == 3, x**3 == 2)
[x = 1.2599210498?, y = -1.1885280594?]
OK,大概了解到它是干嘛的我们就开始看一道例题吧。二进制文件可以在这里下载。
这是whctf的一道逆向题,它的核心代码如下:
v1 = 0;
gets(flag);
for ( i = 0; i <= 35; ++i )
{
if ( !flag[i] )
{
flag[i] = 1;
++v1;
}
}
if ( v1 != 9 )
exit(0);
convert(a);
Transposition(a);
Multi(a, b);
for ( j = 0; j <= 5; ++j )
{
for ( k = 0; k <= 5; ++k )
{
if ( c[0][k + 6 * j] != d[0][k + 6 * j] )
exit(0);
}
}
printf("congratulations!you have gottern the flag!");
Transposition(a)是把a的转置矩阵赋值给b
Multi(a,b)是把a和b的乘积赋值给c
而d就是堆中正确的flag经过上述运算后的结果,也就是说,如果用简单的思路去做,就是想办法爆破27位的flag添加9位1到尾部,然后经过运算结果为d中的值。但未知位数已经达到了20个,常规的爆破思路很难解决,网上的一篇writeup是经过一系列数学运算后逐行爆破,但每行依旧要消耗近10分钟的时间。在实际比赛的过程中,时间始终是最宝贵的,况且如果你对线性代数不太理解,可能会有一些棘手。
首先数学知识当然是必要的,我们应该保持着一个敬畏之心去学习这里的数学原理,但为了节省时间,或许用约束器去做会有意想不到的效果。
以下是我的脚本,注释的很详细就不多说了:
#coding:utf-8
'''
@DateTime: 2017-11-28 10:19:29
@Version: 1.0
@Author: Unname_Bao
'''
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')
t2 = time.time()
print(t2-t1)
这是最终的运行结果:
D:\2017_WEB_Test\ulb_manager\backend\spider>python z3test.py
b'whctf{Y0u_ar3_g00d_a7_m4th}\x01\x01\x01\x01\x01\x01\x01\x01\x01'
4.042840003967285
更多信息请看z3的官方GitHub:点我