z3基本语法
z3是一个约束求解器,可以理解为解方程的工具,每一个约束都要写在x.add的语句中。
声明变量
z3一共有三种变量
1 | a,b = Int('a b')#声明多个整数型变量 |
使用例
1 | a,b = Int('a b') |
1 | x=Int('x') |
1 | from z3 import * |
z3语法
对于普通的自定义逻辑(带位移,异或之类的不好逆向),z3可以秒杀
比如这么写:
1 | from prism import * |
但是可能遇到要对z3型数据判断的情况,比如下面这个题:
1 | int __fastcall main(int argc, const char **argv, const char **envp) |
主函数是这样的,这里它不止对flag进行分类讨论,还在讨论后进行计算:z3型可以直接进行计算,包括加和乘,对于位向量还可以左右移以及异或,但是都不能进行判断大小,如果要进行判断必须要在add中做判断,比如判断两个值是否相等:
1 | f.add(check[2 * i]==out3[2*(len-i-1)]) |
那么如果我们根据这道题写,还需要If函数(Z3自带)
1 | If(condition,a,b) |
意思是:如果条件成立,语句视为a,否则视为b
那么和add结合起来看就是:
1 | f.add(If(And(out[i] > 0x60, out[i] <= 0x7a), out2[i] == (out[i] ^ 0x47) + 1, True)) |
如果条件成立,添加a约束,否则添加b约束,这里的b约束恒为真,表示如果条件不成立就跳过这个约束。可是问题来了,我们知道out[i]是由flag初始化的,也就是说out是z3位向量型,但是out2呢?如果把这里的out2视作一个普通列表,让out[i]计算后的值返回给out2,好像也没问题。但是!!这里的语句并不是赋值语句,而是判断语句,是一个约束
,如果这里out2=34*[0],那么就是让out[i]运算后的值恒等于0的条件成立,显然这是无解的,也就是说,这里的两个变量都必须是位向量才可以,同时,不应该忘了如果两个条件均不成立,就要让out=out2
1 | f.add(out2[i] == If(And(out[i] > 0x40, out[i] <= 0x5a), (out[i] ^ 0x4b) - 1,If(And(out[i] > 0x60, out[i] <= 0x7a), (out[i] ^ 0x47) + 1, out[i]))) |
进行if的嵌套完成这个效果
之后还要对flag继续做变换变成最后的数组,且数组大小翻倍,如果后面的检查比较简单,可以直接在check==的时候写,但是由于这里的比较不是很直观,不如写出来变成单独的列表再比较,这时又要涉及到out2变成out3的问题了,由于这个是直接赋值,没有判断,所以方便起见,直接写赋值语句。
1 | out3[2*i] = LShR(out2[i], 4) |
通过这两个例子可以发现,当想要赋值时,如果要对z3型变量进行判断/比较,就必须用add表达式,在其中,为了达成赋值效果,必须新建一个z3量才可以成功,但是如果是直接赋值,则直接对列表赋值即可。
1 | from prism import * |
1 | "la!{S3ch_A_EZVM} |
以上均是解
补充说明:
如果想约束某个值只能出现一次,比如{,},可以用Sum:
Sum用于计算一系列表达式的和类似于加
1 | s.add(Sum([If(i == BitVecVal(ord('{'), 8), 1, 0) for i in flag]) <= 1) |
计算flag中等于括号的值的表达式个数,然后让其小于等于1