2021ByteCTF-easyxor

题目源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#! /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
#89b8aca257ee2748f030e7f6599cbe0cbb5db25db6d3990d3b752eda9689e30fa2b03ee748e0da3c989da2bba657b912

题目分析

将flag分为两段,前半段采用OFB加密,后半段采用CBC加密

OFB解密

将前半段flag又进行切分,将其8位分为一组,存到group数组中。

1
2
3
4
5
6
#主要加密代码
elif mode == 'OFB':
for eve in groups:
cur_c = convert(last, k)
cipher.append(cur_c ^ eve)
last = cur_c
针对一个随机生成的64位的last,与keys进行convert加密,再与group中的数进行异或。 keys是四个-32~32的数,可爆破。 对于OFB解密十分简单,因为我们已知第一组明文,恰好是8位的'ByteCTF{',由于分组密码的性质,所以后面的也就迎刃而解,通过group[ 0]^cipher[ 0] 即可得到cur_c,从而求得下一次的cur_c,以此类推。

expOFB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#OFB解密
#! /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 check(s):
c=1
for i in s:
if 32<=i<=127:
continue
else:
c=0
break
return c
c = "89b8aca257ee2748f030e7f6599cbe0cbb5db25db6d3990d3b752eda9689e30fa2b03ee748e0da3c989da2bba657b912"
c=c[:len(c)//2]
cipher = []
for i in range(len(c)//16):
cipher.append(int(c[i*16:(i+1)*16],16))
flag = b'ByteCTF{'
m0 = bytes_to_long(flag)
m_m = m0 ^ cipher[0]
for a in range(-32,32):
for b in range(-32,32):
for c in range(-32,32):
for d in range(-32,32):
keys=[a,b,c,d]
m_m1=convert(m_m,keys)
m1=long_to_bytes((m_m1^cipher[1]))
if check(m1):
m_m2 = convert(m_m1, keys)
m2 = long_to_bytes((m_m2 ^ cipher[2]))
if check(m2):
flag+=m1
flag+=m2
print(flag)
print(a,b,c,d)
#b'ByteCTF{5831a241s-f30980'
#keys:-12 26 -3 -31

CBC解密

针对后半部分,我们需要逆向解出convert函数,因此就是对shift函数中的加密运算进行解密。 之前上网搜的wp都一笔带过,没解释unshift逆函数怎么写的。“难道有什么定理??” 这里简单写一下我的理解。

分析shift

1
2
3
4
def shift(m, k, c):
if k < 0:
return m ^ m >> (-k) & c
return m ^ m << k & c

推理过程

这里我们举例k>0时的情况。(k<0同理)

m和c都是64位,k是-32~32的10进制数。

1、m << k相当于在m后补k位0,得到的新数我们称为a,a=m << k。

2、令b = a & c,由于a是64+k位,c是64位,所以b是64位,并且b的后k位都是0。

3、令x = m ^ b,x则是shift加密之后的值,x也是64位,并且x的后k位是与m的后k位相同的,与0异或得本身。

我们走完了一遍shift加密,得到的结论是,密文的位数同样是64,并且密文的后k位与明文相同。

分析unshift

1
2
3
4
5
6
7
8
9
10
def unshift(m, k, c, bits=64):
tmp = m
if k < 0:
for i in range(bits // (-k)):
tmp = m ^ tmp >> (-k) & c
else:
for i in range(bits // k):
tmp = m ^ tmp << k & c
assert shift(tmp, k, c) == m
return tmp

推理过程

同样我们举例k>0的情况。

之前我们得到的密文x,相当于tmp。

1、a'=tmp << k,tmp后k位为0,tmp后2k~k位等于m的后k位。

2、b'=a' & c,b'后k位为0,后2k~k位为m&c。

3、y=x ^ b',y的后k位,等于x的后k位,也就是m的后k位。

4、y的后2k~k位,在shift加密当中,a & c的后2k~k位等于m的后k位&c,记作m1 & c。

shift中的x,也就是m ^ b,m ^(m1 & c),在unshift最后的一步操作中,y= m ^(m1 & c) ^ b'。

这时我们只考虑y的后2k~k位,y=m ^(m1 & c)^(m1 & c) = m

所以这时y的2k~k位也等于m了。

总结

因此,我们发现每经过unshift一次,就有k位被还原,何时才能被完全还原?即循环bits//k次。

此推理正确,可以在unshift函数运行时输出每次的tmp观察即可。

ps:自我感觉这种位运算应该是属于一种性质或者定理,网上大佬们都是说“简单写个逆”。。。

expCBC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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 unconvert(m, key):
tmp = m
c_list = [0x37386180af9ae39e, 0xaf754e29895ee11a, 0x85e1a429a2b7030c, 0x964c5a89f6d3ae8c]
for t in range(3,-1,-1):
m = unshift(m, key[t], c_list[t])
return m
def unshift(m, k, c, bits=64):
tmp = m
if k < 0:
for i in range(bits // (-k)):
tmp = m ^ tmp >> (-k) & c
else:
for i in range(bits // k):

tmp = m ^ tmp << k & c
assert shift(tmp, k, c) == m
return tmp

keys=[-12,26,-3,-31]
c_list = [0x37386180af9ae39e, 0xaf754e29895ee11a, 0x85e1a429a2b7030c, 0x964c5a89f6d3ae8c]
flag=b'ByteCTF{'
flag=bytes_to_long(flag)
iv=16476971533267772345
c = "89b8aca257ee2748f030e7f6599cbe0cbb5db25db6d3990d3b752eda9689e30fa2b03ee748e0da3c989da2bba657b912"
c=c[len(c)//2:]
cipher = []
for i in range(len(c)//16):
cipher.append(int(c[i*16:(i+1)*16],16))
group=[]

curc1=unconvert(cipher[0],keys)
group.append(long_to_bytes(curc1^iv))

curc2=unconvert(cipher[1],keys)
group.append(long_to_bytes(curc2^cipher[0]))

curc3=unconvert(cipher[2],keys)
group.append(long_to_bytes(curc3^cipher[1]))

group[0]+=group[1]
group[0]+=group[2]
print(group[0])
#b'q535af-2156547475u2t}$$$'

2021ByteCTF-easyxor
https://sch01ar.github.io/2022/10/16/2021ByteCTF-easyxor/
作者
Roo1e
发布于
2022年10月16日
许可协议