网站首页 > 技术文章 正文
简介?
Z3 solver 是由微软开发的 可满足性模理论求解器(Satisfiability Modulo Theory solver, 即 SMT solver),用于检查逻辑表达式的可满足性,并可以找到一组约束中的其中一个可行解(无法找出所有的可行解)。
在 CTF 逆向题中,我们有的时候会遇到一些较为复杂的约束条件,此时可以使用 z3 来辅助求解。
安装?
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]
例题:GWCTF 2019 - xxor?
逆向分析?
首先惯例拖入 IDA 中,main() 函数整体逻辑比较简单,首先会读入 6 个整型到栈上,接下来对前三个整型调用三次 sub_400686() 函数进行处理并将结果存到 v7 中,最后调用 sub_400770() 进行检查:
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int i; // [rsp+8h] [rbp-68h]
int j; // [rsp+Ch] [rbp-64h]
__int64 v6[6]; // [rsp+10h] [rbp-60h] BYREF
__int64 v7[6]; // [rsp+40h] [rbp-30h] BYREF
v7[5] = __readfsqword(0x28u);
puts("Let us play a game?");
puts("you have six chances to input");
puts("Come on!");
v6[0] = 0LL;
v6[1] = 0LL;
v6[2] = 0LL;
v6[3] = 0LL;
v6[4] = 0LL;
for ( i = 0; i <= 5; ++i )
{
printf("%s", "input: ");
a2 = (char **)((char *)v6 + 4 * i);
__isoc99_scanf("%d", a2);
}
v7[0] = 0LL;
v7[1] = 0LL;
v7[2] = 0LL;
v7[3] = 0LL;
v7[4] = 0LL;
for ( j = 0; j <= 2; ++j )
{
dword_601078 = v6[j];
dword_60107C = HIDWORD(v6[j]);
a2 = (char **)&unk_601060;
sub_400686(&dword_601078, &unk_601060);
LODWORD(v7[j]) = dword_601078;
HIDWORD(v7[j]) = dword_60107C;
}
if ( (unsigned int)sub_400770(v7, a2) != 1 )
{
puts("NO NO NO~ ");
exit(0);
}
puts("Congratulation!\n");
puts("You seccess half\n");
puts("Do not forget to change input to hex and combine~\n");
puts("ByeBye");
return 0LL;
}
sub_400686() 有点类似于魔改的 TEA 加密:
__int64 __fastcall sub_400686(unsigned int *a1, _DWORD *a2)
{
__int64 result; // rax
unsigned int v3; // [rsp+1Ch] [rbp-24h]
unsigned int v4; // [rsp+20h] [rbp-20h]
int v5; // [rsp+24h] [rbp-1Ch]
unsigned int i; // [rsp+28h] [rbp-18h]
v3 = *a1;
v4 = a1[1];
v5 = 0;
for ( i = 0; i <= 0x3F; ++i )
{
v5 += 1166789954;
v3 += (v4 + v5 + 11) ^ ((v4 << 6) + *a2) ^ ((v4 >> 9) + a2[1]) ^ 0x20;
v4 += (v3 + v5 + 20) ^ ((v3 << 6) + a2[2]) ^ ((v3 >> 9) + a2[3]) ^ 0x10;
}
*a1 = v3;
result = v4;
a1[1] = v4;
return result;
}
这里的参数 a1 为我们的输入,而 a2 则为四个 int32:
.data:0000000000601060 dword_601060 dd 2 ; DATA XREF: main+FE↑o
.data:0000000000601064 dd 2
.data:0000000000601068 dd 3
.data:000000000060106C dd 4
而 sub_400770() 会对处理过后的输入进行检查:
__int64 __fastcall sub_400770(_DWORD *a1)
{
__int64 result; // rax
if ( a1[2] - a1[3] == 2225223423LL
&& a1[3] + a1[4] == 4201428739LL
&& a1[2] - a1[4] == 1121399208LL
&& *a1 == -548868226
&& a1[5] == -2064448480
&& a1[1] == 550153460 )
{
puts("good!");
result = 1LL;
}
else
{
puts("Wrong!");
result = 0LL;
}
return result;
}
求解?
我们首先使用 z3 来求解 sub_400686() 运算后的结果:
import z3
x = [0] * 6
for i in range(6):
x[i] = z3.Int('x[' + str(i) + ']')
s = z3.Solver()
s.add(x[0] == 0xDF48EF7E)
s.add(x[5] == 0x84F30420)
s.add(x[1] == 0x20CAACF4)
s.add(x[2]-x[3] == 0x84A236FF)
s.add(x[3]+x[4] == 0xFA6CB703)
s.add(x[2]-x[4] == 0x42D731A8)
if s.check() == z3.sat:
print(s.model())
else:
raise Exception("NO SOLUTION!")
结果如下:
$ python3 solve.py
[x[2] = 3774025685,
x[3] = 1548802262,
x[4] = 2652626477,
x[1] = 550153460,
x[5] = 2230518816,
x[0] = 3746099070]
接下来我们逆着 sub_400686() 的逻辑写出解密程序即可:
#include <stdio.h>
#include <stdint.h>
void decrypt(uint32_t sum, uint32_t *v, uint32_t *k)
{
uint32_t v0, v1;
v0 = v[0];
v1 = v[1];
for (int i = 0; i < 0x40; i++) {
v1 -= (v0 + sum + 20) ^ ((v0 << 6) + k[2]) ^ ((v0 >> 9) + k[3]) ^ 0x10;
v0 -= (v1 + sum + 11) ^ ((v1 << 6) + k[0]) ^ ((v1 >> 9) + k[1]) ^ 0x20;
sum -= 0x458BCD42;
}
v[0] = v0;
v[1] = v1;
}
int main(int argc, char **argv, char **envp)
{
uint32_t data[] = {
3746099070, 550153460, 3774025685, 1548802262, 2652626477, 2230518816
};
uint32_t sum = 0;
uint32_t k[] = {
2, 2, 3, 4
};
for (int i = 0; i < 0x40; i++) {
sum += 0x458BCD42;
}
for (int i = 0; i < 3; i++) {
decrypt(sum, &data[i * 2], k);
printf("%4x%4x", data[i * 2], data[i * 2 + 1]);
}
puts("");
return 0;
}
结果如下:
$ ./solve
666c61677b72655f69735f6772656174217d
接下来就是快乐的拿 flag 时间了:
s = '666c61677b72655f69735f6772656174217d'
while len(s) != 0:
print(chr(int(s[:2], 16)), end = '')
s = s[2:]
print('')
# flag{re_is_great!}
猜你喜欢
- 2025-01-08 嵌入式中,日志调试法的一些规则!
- 2025-01-08 一行代码改进:Logtail的多行日志采集性能提升7倍的奥秘
- 2025-01-08 嵌入式大杂烩周记 第 7 期:zlog
- 2025-01-08 C语言总结:C语言字符串练习题(十二种习题示例)
- 2025-01-08 C语言100题集合027-二维数组的经典案例,非常重要
- 2025-01-08 一个例子让你看清线程调度的随机性
- 2025-01-08 C++17:结构化绑定
- 2025-01-08 C语言中main函数详解
- 2025-01-08 64TB硬盘容量测试程序(C++)
- 2025-01-08 使用CyberRT写第一个代码
- 02-21走进git时代, 你该怎么玩?_gits
- 02-21GitHub是什么?它可不仅仅是云中的Git版本控制器
- 02-21Git常用操作总结_git基本用法
- 02-21为什么互联网巨头使用Git而放弃SVN?(含核心命令与原理)
- 02-21Git 高级用法,喜欢就拿去用_git基本用法
- 02-21Git常用命令和Git团队使用规范指南
- 02-21总结几个常用的Git命令的使用方法
- 02-21Git工作原理和常用指令_git原理详解
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)