批量替换字符串中的某子串序列为对应的另一子串序列(z3求解器解多元方程时很好用)
标题有点拗口,看问题需求就理解了——
一,问题需求
有一个字符串s1,其中包含a1、a2、a3到a14这些子串,我需要将s1中出现的这些子串全部对应替换成v[0],v[1],v[2]到v[13]等等,应该怎么编写程序
例如:
s1='a1 * 88 + a2 * 67 + a3 * 65 - a4 * 5 + a5 * 43 + a6 * 89 + a7 * 25 + a8 * 13 - a9 * 36 + a10 * 15 + a11 * 11 + a12 * 47 - a13 * 60 + a14 * 29 == 22748'
替换后:
s1='v[0] * 88 + v[1] * 67 + v[2] * 65 - v[3] * 5 + v[4] * 43 + v[5] * 89 + v[6] * 25 + v[7] * 13 - v[8] * 36 + v[9] * 15 + v[10] * 11 + v[11] * 47 - v[12] * 60 + v[13] * 29 == 22748'
二,应用场景
博主在刷ctf的re方向题目时遇到求解多(14)元方程问题,之前其实已经写过一篇z3求解器的相关文章->z3求解器脚本(CTF-reverse必备)_ctf z3-CSDN博客
该脚本用到了列表,默认方程的未知数都是列表中的元素,可以用类似v[index]的形式存储或访问,但是题目中出现的方程一般是以a1、a2或x1、x2等序列形式来表示未知数的,手搓当然可以一个个全部替换,但显然太不优雅了!
因此可以用此脚本对方程中出现的未知数序列统一替换成v[0]、v[1]等序列形式,以便于我们直接套用z3求解器的脚本!
三,完整脚本
只要脚本省时间的可以不看后面的解释了(博主一向这么直白,还望留赞o(* ̄▽ ̄*)ブ)
将题目中出现的一连串方程条件复制给下面这个脚本的s1即可,如果有部分条件不好复制,可以用'+='来拼接不好复制的条件表达式
import re
def replace_func(match):
shift = 2 #shift是指第一个未知数和0的差,例如:如果题目中第一个未知数是v2(如果是v3),那么shift就设置成2(就设置成3)
index = int(match.group(1)) - shift
return str(f'v[{index}]') # 返回字符串'v[a后数字-1]',用其替换匹配到的an
if __name__ == '__main__':
s1 = "245 * v6 + 395 * v5 + 3541 * v4 + 2051 * v3 + 3201 * v2 + 1345 * v7 != 855009| | 3270 * v6 + 3759 * v5 + 3900 * v4 + 3963 * v3 + 1546 * v2 + 3082 * v7 != 1515490| | 526 * v6 + 2283 * v5 + 3349 * v4 + 2458 * v3 + 2012 * v2 + 268 * v7 != 854822| | 3208 * v6 + 2021 * v5 + 3146 * v4 + 1571 * v3 + 2569 * v2 + 1395 * v7 != 1094422| | 3136 * v6 + 3553 * v5 + 2997 * v4 + 1824 * v3 + 1575 * v2 + 1599 * v7 != 1136398| | 2300 * v6 + 1349 * v5 + 86 * v4 + 3672 * v3 + 2908 * v2 + 1681 * v7 != 939991| | 212 * v22 + 153 * v21 + 342 * v20 + 490 * v12 + 325 * v11 + 485 * v10 + 56 * v9 + 202 * v8 + 191 * v23 != 245940| | 348 * v22 + 185 * v21 + 134 * v20 + 153 * v12 + 460 * v9 + 207 * v8 + 22 * v10 + 24 * v11 + 22 * v23 != 146392| | 177 * v22 + 231 * v21 + 489 * v20 + 339 * v12 + 433 * v11 + 311 * v10 + 164 * v9 + 154 * v8 + 100 * v23 != 239438| | 68 * v20 + 466 * v12 + 470 * v11 + 22 * v10 + 270 * v9 + 360 * v8 + 337 * v21 + 257 * v22 + 82 * v23 != 233887| | 246 * v22 + 235 * v21 + 468 * v20 + 91 * v12 + 151 * v11 + 197 * v8 + 92 * v9 + 73 * v10 + 54 * v23 != 152663| | 241 * v22 + 377 * v21 + 131 * v20 + 243 * v12 + 233 * v11 + 55 * v10 + 376 * v9 + 242 * v8 + 343 * v23 != 228375| | 356 * v22 + 200 * v21 + 136 * v11 + 301 * v10 + 284 * v9 + 364 * v8 + 458 * v12 + 5 * v20 + 61 * v23 != 211183| | 154 * v22 + 55 * v21 + 406 * v20 + 107 * v12 + 80 * v10 + 66 * v8 + 71 * v9 + 17 * v11 + 71 * v23 != 96788| | 335 * v22 + 201 * v21 + 197 * v11 + 280 * v10 + 409 * v9 + 56 * v8 + 494 * v12 + 63 * v20 + 99 * v23 != 204625| | 428 * v18 + 1266 * v17 + 1326 * v16 + 1967 * v15 + 3001 * v14 + 81 * v13 + 2439 * v19 != 1109296| | 2585 * v18 + 4027 * v17 + 141 * v16 + 2539 * v15 + 3073 * v14 + 164 * v13 + 1556 * v19 != 1368547| | 2080 * v18 + 358 * v17 + 1317 * v16 + 1341 * v15 + 3681 * v14 + 2197 * v13 + 1205 * v19 != 1320274| | 840 * v18 + 1494 * v17 + 2353 * v16 + 235 * v15 + 3843 * v14 + 1496 * v13 + 1302 * v19 != 1206735| | 101 * v18 + 2025 * v17 + 2842 * v16 + 1559 * v15 + 2143 * v14 + 3008 * v13 + 981 * v19 != 1306983| | 1290 * v18 + 3822 * v17 + 1733 * v16 + 292 * v15 + 816 * v14 + 1017 * v13 + 3199 * v19 != 1160573" # 定义包含an的字符串
s1 += '| | 186 * v18 + 2712 * v17 + 2136 * v16 + 98 * v13 + 138 * v14 + 3584 * v15 + 1173 * v19 == 1005746'
s1 = re.sub(r'v(2[0-9]|1[0-9]|[1-9])', replace_func, s1)
# sub函数参数, pattern、repl、string分别表示:正则表达式匹配规则、替换后结果(可以是函数也可以是常量)、要被查找替换的原始字符串
s1 = re.sub('!', '=', s1) #有些题目给的条件的方程是用'||'关系运算符连接的不等式方程,需要用这一行代码将'!'替换成'='变成等式方程
res = s1.split('| | ')
print(res)
四,脚本详解
该脚本的主要难点在于re(正则表达式)模块的相关API的使用
s1是原始字符串,我们希望将其中的a1、a2...a14等序列替换为对应的v[0]、v[1]...v[13],以便于直接复制该脚本替换后的字符串作为z3求解器脚本的约束方程组
你需要了解sub()函数的机理——Python 正则表达式 | 菜鸟教程 (runoob.com)
省流解释——re.sub(pattern, repl, string)
sub函数参数, pattern、repl、string分别表示:正则表达式匹配规则、替换后结果(可以是函数也可以是常量)、要被查找替换的原始字符串
此处pattern参数为r'v(2[0-9]|1[0-9]|[1-9])',意味匹配a后(我知道是v后,v和a无所谓,你根据题目出现的变量名自行修改就是了)带整数20到29、10到19或1到9的子串,请注意,2[0-9]与1[0-9]必须写于[1-9]之前,否则会出现a10中的a1被错误匹配成v[0]的情况导致替换结果为v[0]0
s1 = re.sub(r'v(2[0-9]|1[0-9]|[1-9])', replace_func, s1)
替换结果函数
当参数repl是固定值时被匹配的子串都会替换成固定值,但是我们希望实现的是
a1->v[0]、a2->v[1]的映射,因此使用函数可以实现复杂替换
此处的match.group(1)是第一个括号匹配部分,在此处即a后的数字1到14
如果是match.group(0)则是整个匹配到的子串,即a0、a1...a14
获取到a后的数字,根据替换的映射关系减一作为新的序列编号,返回格式为v[index]的新序列,这些序列中的v[0]、v[1]...v[13]将对应替换匹配到的a1、a2...a14
最后直接输出替换后的结果即可
def replace_func(match): shift = 2 #shift是指第一个未知数和0的差,例如:如果题目中第一个未知数是v2(如果是v3),那么shift就设置成2(就设置成3) index = int(match.group(1)) - shift return str(f'v[{index}]') # 返回字符串'v[a后数字-1]',用其替换匹配到的an
五,例题放送
[羊城杯 2020]login
题目链接->NSSCTF | 在线CTF平台
import sys
input1 = input("input something:")
if len(input1) != 14:
print("Wrong length!")
sys.exit()
else:
code = []
for i in range(13):
code.append(ord(input1[i]) ^ ord(input1[i + 1]))
code.append(ord(input1[13]))
a1 = code[2]
a2 = code[1]
a3 = code[0]
a4 = code[3]
a5 = code[4]
a6 = code[5]
a7 = code[6]
a8 = code[7]
a9 = code[9]
a10 = code[8]
a11 = code[10]
a12 = code[11]
a13 = code[12]
a14 = code[13]
if (a1 * 88 + a2 * 67 + a3 * 65 - a4 * 5 + a5 * 43 + a6 * 89 + a7 * 25 + a8 * 13 - a9 * 36 + a10 * 15 + a11 * 11 + a12 * 47 - a13 * 60 + a14 * 29 == 22748) & (a1 * 89 + a2 * 7 + a3 * 12 - a4 * 25 + a5 * 41 + a6 * 23 + a7 * 20 - a8 * 66 + a9 * 31 + a10 * 8 + a11 * 2 - a12 * 41 - a13 * 39 + a14 * 17 == 7258) & (a1 * 28 + a2 * 35 + a3 * 16 - a4 * 65 + a5 * 53 + a6 * 39 + a7 * 27 + a8 * 15 - a9 * 33 + a10 * 13 + a11 * 101 + a12 * 90 - a13 * 34 + a14 * 23 == 26190) & (a1 * 23 + a2 * 34 + a3 * 35 - a4 * 59 + a5 * 49 + a6 * 81 + a7 * 25 + (a8 << 7) - a9 * 32 + a10 * 75 + a11 * 81 + a12 * 47 - a13 * 60 + a14 * 29 == 37136) & (a1 * 38 + a2 * 97 + a3 * 35 - a4 * 52 + a5 * 42 + a6 * 79 + a7 * 90 + a8 * 23 - a9 * 36 + a10 * 57 + a11 * 81 + a12 * 42 - a13 * 62 - a14 * 11 == 27915) & (a1 * 22 + a2 * 27 + a3 * 35 - a4 * 45 + a5 * 47 + a6 * 49 + a7 * 29 + a8 * 18 - a9 * 26 + a10 * 35 + a11 * 41 + a12 * 40 - a13 * 61 + a14 * 28 == 17298) & (a1 * 12 + a2 * 45 + a3 * 35 - a4 * 9 - a5 * 42 + a6 * 86 + a7 * 23 + a8 * 85 - a9 * 47 + a10 * 34 + a11 * 76 + a12 * 43 - a13 * 44 + a14 * 65 == 19875) & (a1 * 79 + a2 * 62 + a3 * 35 - a4 * 85 + a5 * 33 + a6 * 79 + a7 * 86 + a8 * 14 - a9 * 30 + a10 * 25 + a11 * 11 + a12 * 57 - a13 * 50 - a14 * 9 == 22784) & (a1 * 8 + a2 * 6 + a3 * 64 - a4 * 85 + a5 * 73 + a6 * 29 + a7 * 2 + a8 * 23 - a9 * 36 + a10 * 5 + a11 * 2 + a12 * 47 - a13 * 64 + a14 * 27 == 9710) & (a1 * 67 - a2 * 68 + a3 * 68 - a4 * 51 - a5 * 43 + a6 * 81 + a7 * 22 - a8 * 12 - a9 * 38 + a10 * 75 + a11 * 41 + a12 * 27 - a13 * 52 + a14 * 31 == 13376) & (a1 * 85 + a2 * 63 + a3 * 5 - a4 * 51 + a5 * 44 + a6 * 36 + a7 * 28 + a8 * 15 - a9 * 6 + a10 * 45 + a11 * 31 + a12 * 7 - a13 * 67 + a14 * 78 == 24065) & (a1 * 47 + a2 * 64 + a3 * 66 - a4 * 5 + a5 * 43 + a6 * 112 + a7 * 25 + a8 * 13 - a9 * 35 + a10 * 95 + a11 * 21 + a12 * 43 - a13 * 61 + a14 * 20 == 27687) & (a1 * 89 + a2 * 67 + a3 * 85 - a4 * 25 + a5 * 49 + a6 * 89 + a7 * 23 + a8 * 56 - a9 * 92 + a10 * 14 + a11 * 89 + a12 * 47 - a13 * 61 - a14 * 29 == 29250) & (a1 * 95 + a2 * 34 + a3 * 62 - a4 * 9 - a5 * 43 + a6 * 83 + a7 * 25 + a8 * 12 - a9 * 36 + a10 * 16 + a11 * 51 + a12 * 47 - a13 * 60 - a14 * 24 == 15317):
print("flag is GWHT{md5(your_input)}")
print("Congratulations and have fun!")
else:
print("Sorry,plz try again...")
wp:
本题考察多元方程求解,可以使用z3求解器->z3求解器脚本(CTF-reverse必备)_ctf z3-CSDN博客
先用脚本把这一大串条件处理成列表
注意,第4个方程中出现了<<7,z3求解不支持位移操作,将其替换为等价的*128即可
然后把整个由方程组成的列表赋值给下面这个脚本的fc列表,然后运行就能解方程
解出方程后不要太激动,仔细审题,还有一部异或操作,要异或回去才是正确答案,所以先把方程解存在列表里
最后输出的结果根据题目是要md5值,说明不是具体的字符串,那就直接打印整数,然后用hashlib模块中的API进行md5加密
from z3 import *
import hashlib # 题目最后得到的字符串要md5加密
def solver_eng(fc):
# 创建解释器对象
solver = Solver()
# 添加约束方程
for i in range(len(fc)):
solver.add(eval(fc[i])) #eval函数会将字符串形式的方程转换为z3模块能解析的方程
# 求解并转化为字符输出,得到flag
solution = []
if solver.check() == sat: # check()方法用来判断是否有解,sat(即satisify)表示满足有解
ans = solver.model() # model()方法得到解
for i in v:
solution.append(ans[i].as_long())
# 一般不会无解,如果无解八成是未知数变量的类型不符合,或约束方程添加错误
else:
print("no ans!")
return solution
if __name__ == '__main__':
# 设置方程,请用脚本1将条件中的所有方程处理成列表,然后赋值给fc列表(这样你就不用一个一个方程慢慢去复制了)
fc = ['v[0] * 88 + v[1] * 67 + v[2] * 65 - v[3] * 5 + v[4] * 43 + v[5] * 89 + v[6] * 25 + v[7] * 13 - v[8] * 36 + v[9] * 15 + v[10] * 11 + v[11] * 47 - v[12] * 60 + v[13] * 29 == 22748', 'v[0] * 89 + v[1] * 7 + v[2] * 12 - v[3] * 25 + v[4] * 41 + v[5] * 23 + v[6] * 20 - v[7] * 66 + v[8] * 31 + v[9] * 8 + v[10] * 2 - v[11] * 41 - v[12] * 39 + v[13] * 17 == 7258', 'v[0] * 28 + v[1] * 35 + v[2] * 16 - v[3] * 65 + v[4] * 53 + v[5] * 39 + v[6] * 27 + v[7] * 15 - v[8] * 33 + v[9] * 13 + v[10] * 101 + v[11] * 90 - v[12] * 34 + v[13] * 23 == 26190', 'v[0] * 23 + v[1] * 34 + v[2] * 35 - v[3] * 59 + v[4] * 49 + v[5] * 81 + v[6] * 25 + v[7] * 128 - v[8] * 32 + v[9] * 75 + v[10] * 81 + v[11] * 47 - v[12] * 60 + v[13] * 29 == 37136', 'v[0] * 38 + v[1] * 97 + v[2] * 35 - v[3] * 52 + v[4] * 42 + v[5] * 79 + v[6] * 90 + v[7] * 23 - v[8] * 36 + v[9] * 57 + v[10] * 81 + v[11] * 42 - v[12] * 62 - v[13] * 11 == 27915', 'v[0] * 22 + v[1] * 27 + v[2] * 35 - v[3] * 45 + v[4] * 47 + v[5] * 49 + v[6] * 29 + v[7] * 18 - v[8] * 26 + v[9] * 35 + v[10] * 41 + v[11] * 40 - v[12] * 61 + v[13] * 28 == 17298', 'v[0] * 12 + v[1] * 45 + v[2] * 35 - v[3] * 9 - v[4] * 42 + v[5] * 86 + v[6] * 23 + v[7] * 85 - v[8] * 47 + v[9] * 34 + v[10] * 76 + v[11] * 43 - v[12] * 44 + v[13] * 65 == 19875', 'v[0] * 79 + v[1] * 62 + v[2] * 35 - v[3] * 85 + v[4] * 33 + v[5] * 79 + v[6] * 86 + v[7] * 14 - v[8] * 30 + v[9] * 25 + v[10] * 11 + v[11] * 57 - v[12] * 50 - v[13] * 9 == 22784', 'v[0] * 8 + v[1] * 6 + v[2] * 64 - v[3] * 85 + v[4] * 73 + v[5] * 29 + v[6] * 2 + v[7] * 23 - v[8] * 36 + v[9] * 5 + v[10] * 2 + v[11] * 47 - v[12] * 64 + v[13] * 27 == 9710', 'v[0] * 67 - v[1] * 68 + v[2] * 68 - v[3] * 51 - v[4] * 43 + v[5] * 81 + v[6] * 22 - v[7] * 12 - v[8] * 38 + v[9] * 75 + v[10] * 41 + v[11] * 27 - v[12] * 52 + v[13] * 31 == 13376', 'v[0] * 85 + v[1] * 63 + v[2] * 5 - v[3] * 51 + v[4] * 44 + v[5] * 36 + v[6] * 28 + v[7] * 15 - v[8] * 6 + v[9] * 45 + v[10] * 31 + v[11] * 7 - v[12] * 67 + v[13] * 78 == 24065', 'v[0] * 47 + v[1] * 64 + v[2] * 66 - v[3] * 5 + v[4] * 43 + v[5] * 112 + v[6] * 25 + v[7] * 13 - v[8] * 35 + v[9] * 95 + v[10] * 21 + v[11] * 43 - v[12] * 61 + v[13] * 20 == 27687', 'v[0] * 89 + v[1] * 67 + v[2] * 85 - v[3] * 25 + v[4] * 49 + v[5] * 89 + v[6] * 23 + v[7] * 56 - v[8] * 92 + v[9] * 14 + v[10] * 89 + v[11] * 47 - v[12] * 61 - v[13] * 29 == 29250', 'v[0] * 95 + v[1] * 34 + v[2] * 62 - v[3] * 9 - v[4] * 43 + v[5] * 83 + v[6] * 25 + v[7] * 12 - v[8] * 36 + v[9] * 16 + v[10] * 51 + v[11] * 47 - v[12] * 60 - v[13] * 24 == 15317']
# 创建未知数变量
v = [Int(f'v{i}') for i in range(0, len(fc))]
solution = solver_eng(fc)
print(solution)
# 分析题目得知,解和输入的字符顺序不是一一对应的,需要按输入字符串code的索引顺序重新排列解的顺序
index = [2, 1, 0, 3, 4, 5, 6, 7, 9, 8, 10, 11, 12, 13]
data = []
for i in range(len(index)):
# 按index的顺序重排solution元素,新元素存入data[]中,注意不能直接放回solution[],细想一下就明白了
data.append(solution[index[i]])
for i in range(12, -1, -1):
# code.append(ord(input1[i]) ^ ord(input1[i + 1])) 异或解密
data[i] ^= data[i + 1]
flag = ''
for i in data:
flag += chr(i) # 结果:U_G07_th3_k3y!,题目说用md5加密才是flag
# 下面来进行md5加密
md5 = hashlib.md5() # 创建md5对象
md5.update(flag.encode()) # 更新md5对象的信息,必须传入bytes类型的数据(b'xxx'格式)
print(md5.hexdigest()) # 获取加密后的内容:58964088b637e50d3a22b9510c1d1ef8