BtyeCTF 2021 Crypto-easyxor Write up
第一次打字节跳动的比赛,题目质量真的高且难,密码只搞懂了一道题。
题目:
#! /usr/bin/env python
from Crypto.Util.number import bytes_to_long, long_to_bytes
from random import randint, getrandbits
def shift(m, k, c):
if k < 0:
return m ^ m >> (-k) & c
return m ^ m << k & c
def convert(m, key):
c_list = [0x37386180af9ae39e, 0xaf754e29895ee11a, 0x85e1a429a2b7030c, 0x964c5a89f6d3ae8c]
for t in range(4):
m = shift(m, key[t], c_list[t])
return m
def encrypt(m, k, iv, mode='CBC'):
assert len(m) % 8 == 0
num = len(m) // 8
groups = []
for i in range(num):
groups.append(bytes_to_long(m[i * 8: (i + 1) * 8]))
last = iv
cipher = []
if mode == 'CBC':
for eve in groups:
cur = eve ^ last
cur_c = convert(cur, k)
cipher.append(cur_c)
last = cur_c
elif mode == 'OFB':
for eve in groups:
cur_c = convert(last, k)
cipher.append(cur_c ^ eve)
last = cur_c
else:
print 'Not supported now!'
return ''.join([hex(eve)[2:].strip('L').rjust(16, '0') for eve in cipher])
if __name__ == '__main__':
from secret import flag
if len(flag) % 8 != 0:
flag += '$' * (8 - len(flag) % 8)
length = len(flag)
num = length // 8
keys = [randint(-32, 32) for _ in range(4)]
IV = getrandbits(64)
front = flag[:length // 2]
back = flag[length // 2:]
cipher1 = encrypt(front, keys, IV, mode='OFB')
cipher2 = encrypt(back, keys, IV)
print cipher1 + cipher2
分析题目:
典型的分组密码但是其中掺杂流加密,查看主函数得知明文分两半加密,前半段模式为OFB,后半段为CBC,而且flag长度是8的整数倍,如果原flag不足8的整数倍结尾填充字符’$’。
每半段又分成三组进行对应模式的加密,三组小密文hex之后插入队列中,转字符串之后两端拼接形成完整密文。
前半段:
elif mode == 'OFB':
for eve in groups:
cur_c = convert(last, k)
cipher.append(cur_c ^ eve)
last = cur_c
flag头为BtyeCTF,所以前半段flag的第一组必定是’BtyeCTF{’,所以bytes_to_long之后与其对应的密文异或后得到cur_c,cur_c又将值赋给last,给第二组加密,这时候就已知last,所以爆破key就好了。
exp(前半段):
from Crypto.Util.number import *
import string
def shift(m, k, c):
if k < 0:
return m ^ m >> (-k) & c
return m ^ m << k & c
def convert(m, key):
c_list = [0x37386180af9ae39e, 0xaf754e29895ee11a, 0x85e1a429a2b7030c, 0x964c5a89f6d3ae8c]
for t in range(4):
m = shift(m, key[t], c_list[t])
return m
c1 = [9923871592170792776, 17307588413236231692, 13501143373495638285]
m1 = b'ByteCTF{'
m1 = bytes_to_long(m1)
eve1 = m1 ^ c1[0]
print(eve1)
f = open('result1.txt','w')
ff = open('result2.txt','w')
for key0 in range(-32,33):
for key1 in range(-32,33):
for key2 in range(-32, 33):
for key3 in range(-32, 33):
key=[key0,key1,key2,key3]
cur_c1 = convert(eve1,key)
m = c1[1] ^ cur_c1
flag = long_to_bytes(m)
try:
if all(c in string.printable for c in flag.decode()):#筛去不可打印字符
f.write(flag.decode() + str(key) + '\n')
except:
pass
#创建特殊字符集a,并筛选爆破结果
f.close()
a = ['"','*','~','&','$','\\','|','!','{','}','#','^','?','=','(',')','@','>','<','%','+',':',';','`','/','.']
f = open('out.txt','r')
for i in f.readlines():
if all(x not in a for x in i):
ff.write(i+'\n')
ff.close()
f.close()
#筛过后剩余大约500项左右,由于见过的flag都是有一定特征或规律性,不可能是大小写字母与数字混杂的乱码,所以找出一个只含小写字母和数字的串,拿到它对应的key[]
key = [-12,26,-3,-31]
front = b''
for i in c1:
front+=long_to_bytes(eve1 ^ i)
eve1 = convert(eve1,key)
print(front)
#ByteCTF{5831a241s-f30980
后半段:
if mode == 'CBC':
for eve in groups:
cur = eve ^ last
cur_c = convert(cur, k)
cipher.append(cur_c)
last = cur_c
拿到key了就好办了,用z3库一把梭就好了。
exp(后半段):
from Crypto.Util.number import *
from z3 import *
key = [-12,26,-3,-31]
c2 = [4284382136876262159, 11722938993126464060, 10997124791941970194]
c_list = [0x37386180af9ae39e, 0xaf754e29895ee11a, 0x85e1a429a2b7030c, 0x964c5a89f6d3ae8c]
ivc2=[14682254609762378035]+c2#将前半段中的eve1与c2一起带入求解,可以求得iv和cur
for i in ivc2:
s=Solver()#创建约束求解器
m=BitVec('m', 100)#定义一个100比特的变量
def shift(m, key, c_list):
for i in range(4):
if key[i]<0:
m=m ^ m >> (-key[i]) & c_list[i]
else:
m=m ^ m << key[i] & c_list[i]
return m
s.add(shift(m,key,c_list)==i)#约束求解条件
if s.check()==sat:#如果有解
print(s.model())#输出解,且仅有一组解
iv= 16476971533267772345
last=[16476971533267772345,c2[0],c2[1]]
cur=[10780708739817148043,738617756395427640,10936161096540945944]
back=b''
for i in range(3):
back+=long_to_bytes(cur[i]^last[i])
print(back)
#q535af-2156547475u2t}
拼接后得到 ByteCTF{5831a241s-f30980q535af-2156547475u2t}。