Z3在CTF逆向中的运用

您所在的位置:网站首页 ctf逆向题 Z3在CTF逆向中的运用

Z3在CTF逆向中的运用

2024-03-01 22:11| 来源: 网络整理| 查看: 265

前言

Z3是Microsoft Research开发的高性能定力证明器,Z3拥有者非常广泛的应用场景:软件/硬件验证和测试,约束求解,混合系统分析,安全性研究,生物学研究(计算机分析)以及几何问题。Z3Py是使用Python脚本来解决一些实际问题,Z3Py在windows下的安装可以参考如下链接:z3py在win下安装。Z3Py的使用教学可以参考如下z3py的使用教程 kali系统 python3自带z3

CTF逆向中的应用

现在的CTF逆向中,求解方程式或者求解约束条件是非常常见的一种考察方式,而ctf比赛都是限时的,当我们已经逆向出来flag的约束条件时,可能还需要花一定的时间去求解逆过程。而Z3求解器就给我们提供了一个非常便利求解方式,我们只需要定义未知量(x,y等),然后为这些未知量添加约束方式即可求解。Z3求解器能够求解任意多项式,但是要注意的是,当方程的方式为2**x这种次方运算的时候,方程式已经不是多项式的范畴了,Z3便无法求解

基本使用

现在我们利用官方文档中的一个例子来粗略的看一下Z3Py的使用。

x=Int('x') y=Int('y') solve(x>2,y 2 y < 10 x + 2*y == 7 由上述的代码看得出来Z3Py的使用方式比较简单, 定义未知量 添加约束条件 然后求解

CTF中的示例

XXX比赛中的逆向题 首先我们利用IDA去打开该文件,定位到关键点,发现关键函数如下:

signed __int64 sub_400766() { if ( strlen((const char *)&stru_6020A0) != 32 ) return 0LL; v3 = stru_6020A0.y1; v4 = stru_6020A0.y2; v5 = stru_6020A0.y3; v6 = stru_6020A0.y4; if ( stru_6020A0.x2 * (signed __int64)stru_6020A0.x1 - stru_6020A0.x4 * (signed __int64)stru_6020A0.x3 != 0x24CDF2E7C953DA56LL ) goto LABEL_15; if ( 3LL * stru_6020A0.x3 + 4LL * stru_6020A0.x4 - stru_6020A0.x2 - 2LL * stru_6020A0.x1 != 0x17B85F06 ) goto LABEL_15; if ( 3 * stru_6020A0.x1 * (signed __int64)stru_6020A0.x4 - stru_6020A0.x3 * (signed __int64)stru_6020A0.x2 != 0x2E6E497E6415CF3ELL ) goto LABEL_15; if ( 27LL * stru_6020A0.x2 + stru_6020A0.x1 - 11LL * stru_6020A0.x4 - stru_6020A0.x3 != 0x95AE13337LL ) goto LABEL_15; srand(stru_6020A0.x3 ^ stru_6020A0.x2 ^ stru_6020A0.x1 ^ stru_6020A0.x4); v1 = rand() % 50; v2 = rand() % 50; v7 = rand() % 50; v8 = rand() % 50; v9 = rand() % 50; v10 = rand() % 50; v11 = rand() % 50; v12 = rand() % 50; if ( v6 * v2 + v3 * v1 - v4 - v5 != 0xE638C96D3LL || v6 + v3 + v5 * v8 - v4 * v7 != 0xB59F2D0CBLL || v3 * v9 + v4 * v10 - v5 - v6 != 0xDCFE88C6DLL || v5 * v12 + v3 - v4 - v6 * v11 != 0xC076D98BBLL ) { LABEL_15: result = 0LL; } else { result = 1LL; } return result; }

可以看得出来这个题目的目的就是找出满足方程的flag。我们可以很方便的把方程式列出来,但是求解对于一些数学不是很好的人来说简直就是噩梦,这时候Z3求解器就可以很方便的给我们帮助。我们按照题目的意思一步一步利用Z3求解器来求解:

from z3 import * x1=Int('x1') x2=Int('x2') x3=Int('x3') x4=Int('x4') s=Solver s.add(x2*x1-x4*x3== 0x24CDF2E7C953DA56) s.add( x2*x1-x4*x3 == 0x24CDF2E7C953DA56) s.add( 3*x3+4*x4-x2-2*x1 == 0x17B85F06) s.add( 3*x1*x4-x3*x2 == 0x2E6E497E6415CF3E) s.add( 27*x2+x1-11*x4 - x3 == 0x95AE13337) print (s.check()) m = s.model() print ("traversing model...") for d in m.decls(): print ("%s = %s" % (d.name(), m[d]))

Solver()命令创建一个通用求解器。我们可以通过add函数添加约束条件。我们称之为声明约束条件。check()函数解决声明的约束条件,sat结果表示找到某个合适的解,unsat结果表示没有解。这时候我们称约束系统无解。最后,求解器可能无法解决约束系统并返回未知作为结果。 对于上面的题目我们首先定义x1,x2,x3,x4四个int变量,然后添加逆向中的约束条件,最后进行求解。Z3会在找到合适解的时候返回sat。我们认为Z3能够满足这些约束条件并得到解决方案。该解决方案被看做一组解决约束条件的模型。模型能够使求解器中的每个约束条件都成立。最后我们遍历model中的解。 得到x1,x2,x3,x4的解后,我们将其代入逆向题中,得出v1,v2,v7,v8,v9,v9,v10,v11,v12的值,然后进行下一步的求解:

v1 = 0x16 v2 = 0x27 v7 = 0x2d v8= 0x2d v9 = 0x23 v10= 0x29 v11 = 0xd v12 = 0x24 v3 = Int('v3') v4 = Int('v4') v5 = Int('v5') v6 = Int('v6') s = Solver() s.add(v6 * v2 + v3 * v1 - v4 - v5 == 0xE638C96D3) s.add(v6 + v3 + v5 * v8 - v4 * v7 == 0xB59F2D0CB) s.add(v3 * v9 + v4 * v10 - v5 - v6 == 0xDCFE88C6D) s.add(v5 * v12 + v3 - v4 - v6 * v11 == 0xC076D98BB) print (s.check()) m = s.model() print ("traversing model...") for d in m.decls(): print ("%s = %s" % (d.name(), m[d]))

这样的话我们就花了比较少的时间得到我们想要的flag,还是比较方便的。 但是现实中很多的逆向题都是基于位运算的,同样在Z3Py中可以使用Bit_Vectors进行机器运算。它们能够实现无符号和有符号二进制运算。Z3为符号数运算提供了一个特殊的运算符操作版本,其中运算符 =,/,%和>>对应于有符号运算。 相应的无符号运算符是ULT,ULE,UGT,UGE,UDiv,URem和LShR。我们看一下如下的代码就能清楚许多:

# Create to bit-vectors of size 32 x, y = BitVecs('x y', 32) solve(x + y == 2, x > 0, y > 0) # Bit-wise operators # & bit-wise and # | bit-wise or # ~ bit-wise not solve(x & y == ~y) solve(x Y[i] X_gt_Y = [ X[i] > Y[i] for i in range(5) ] print (X_gt_Y) print (And(X_gt_Y)) # Create a 3x3 "matrix" (list of lists) of integer variables X = [ [ Int("x_%s_%s" % (i+1, j+1)) for j in range(3) ] for i in range(3) ] pp(X)

在上面的例子中,表达式“x%s”%i返回一个字符串,其中%s被替换为i的值。命令pp与print类似,但是它使用Z3Py格式化程序而不是Python的格式化程序来使用列表和元组。

第八届极客大挑战的REConvolution

我们打开文件,也是比较直观的看到约束条件,我试着逆向了这个过程,花费了挺多的时间才得到答案,但是如果我们使用Z3Py来求解的话就会非常的快。 函数关键部分如下:

int __cdecl main(int argc, const char **argv, const char **envp) { unsigned int ii; // esi unsigned int v4; // kr00_4 char flag_i; // bl unsigned int jj; // eax char *v7; // edx char v8; // cl int v9; // eax char xor_result[80]; // [esp+8h] [ebp-A4h] char flag[80]; // [esp+58h] [ebp-54h] sub_DC1020("Please input your flag: "); sub_DC1050("%40s", flag); memset(xor_result, 0, 0x50u); ii = 0; v4 = strlen(flag); if ( v4 ) { do { flag_i = flag[ii]; jj = 0; do { v7 = &xor_result[jj + ii]; v8 = flag_i ^ data1[jj++]; *v7 += v8; } while ( jj


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3