记一手NCTF出题

被突零✌抓住要给NCTF出一道签到Crypto题捏

出题思路

翻各种知识点,看到OFB的时候联想到这跟一次一密挺像的嘛 如果把OFB的初始向量IV固定下来这不就是道MTP了(开始水题目)

题目源码

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import socketserver
from Crypto.Util.number import *
from Crypto.Cipher import AES
import os
import signal
import string
import hashlib
from random import *
banner = br"""
__ __ _ _ _ _ ____ _____ _____ ____ ___ ____ ____
\ \ / /__| | ___ ___ _ __ ___ ___ | |_ ___ | \ | |/ ___|_ _| ___|___ \ / _ \___ \|___ \
\ \ /\ / / _ \ |/ __/ _ \| '_ ` _ \ / _ \ | __/ _ \ | \| | | | | | |_ __) | | | |__) | __) |
\ V V / __/ | (_| (_) | | | | | | __/ | || (_) | | |\ | |___ | | | _| / __/| |_| / __/ / __/
\_/\_/ \___|_|\___\___/|_| |_| |_|\___| \__\___/ |_| \_|\____| |_| |_| |_____|\___/_____|_____|

"""

class Task(socketserver.BaseRequestHandler):
def encode(self):
message = open('message.txt', 'r').read()
key = os.urandom(16)
IV = os.urandom(16)
message = [message[i*32:(i+1)*32] for i in range(len(message) // 32 + 1)]
cipher = b""
for msg in message:
aes = AES.new(key, AES.MODE_OFB, IV)
cipher += aes.encrypt(msg.encode())
return cipher.hex()

def send(self, msg, newline=True):
if newline:
msg += b"\n"
self.request.sendall(msg)
def _recvall(self):
BUFF_SIZE = 2048
data = b''
while True:
part = self.request.recv(BUFF_SIZE)
data += part
if len(part) < BUFF_SIZE:
break
return data.strip()
def recv(self, prompt=b'> '):
self.send(prompt, newline=False)
return self._recvall()

def handle(self):
signal.alarm(1200)
self.send(banner)
self.send(b"This is your cipher")
c = self.encode()
self.send(c.encode())
self.send(b"Plz tell me my the md5(message):")
md5 = self.recv()
if md5.decode() == hashlib.md5(open('message.txt', 'rb').read()).hexdigest():
self.send(b"Congratulation!\nHere is your flag")
self.send(open('flag.txt', 'rb').read())


class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass


class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
pass


if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 10001
while 1:
try:
server = ForkedServer((HOST, PORT), Task)
server.allow_reuse_address = True
print("Server at 0.0.0.0 port "+str(PORT))
server.serve_forever()
except:
PORT = PORT+1

解题思路

根据OFB加密模式的工作原理,由于题目中每次加密都使用了相同的IV,也就意味着每次加密都相当于与一串固定的密钥流进行异或。我们可以将本题看作是一个Many-Time-Pad题目。

对Many-Time-Pad的攻击可以参考这篇文章Many-Time-Pad 攻击 (ruanx.net)

如果在一开始注意到了题目用相同的IV和OFB加密模式并且对其有所了解,不难看出这就是个ManyTimePad的变形罢了。连上服务器拿到密文,按64一组分开然后就是MTP的操作咯~

exp:

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
55
56
57
58
59
60
61
62
import Crypto.Util.strxor as xo
import libnum, codecs, numpy as np
from hashlib import md5


def isChr(x):
if ord('a') <= x and x <= ord('z'): return True
if ord('A') <= x and x <= ord('Z'): return True
return False


def infer(index, pos):
if msg[index, pos] != 0:
return
msg[index, pos] = ord(' ')
for x in range(len(c)):
if x != index:
msg[x][pos] = xo.strxor(c[x], c[index])[pos] ^ ord(' ')

def know(index, pos, ch):
msg[index, pos] = ord(ch)
for x in range(len(c)):
if x != index:
msg[x][pos] = xo.strxor(c[x], c[index])[pos] ^ ord(ch)


dat = []

def getSpace():
for index, x in enumerate(c):
res = [xo.strxor(x, y) for y in c if x!=y]
f = lambda pos: len(list(filter(isChr, [s[pos] for s in res])))
cnt = [f(pos) for pos in range(len(x))]
for pos in range(len(x)):
dat.append((f(pos), index, pos))

if __name__=="__main__":
c = '8781e362ed6919575946b0e38757a632ff85c6ea5ec9683c7f1c2f116243145eb282e331a27d4d45405df3eec251ab20f683dfea1fe85a11765d620d7f48575ba186e82df76f4d545840f5e48f12a139ee8ec8b858a6670a765b271063545547b69aa629e7651e535e57f1e8c250ae3ffd8ddee656f146173554621f74431447bb8ce862da533f424812e7ec965ae224f6838dba1ae7471022593a0a2644585cb082f562f6734d404946b0f18a57e233f796c5af04f24b062212623473554013b29aa635eb6805074346f8e09012b124ec83cca756e5470e3e59300d2a06525fba99f62bec7b4d460c50f9f1c25bac70ea8ec8ea15ef5e16334e361b7e521443a186e237e1791e074d12f6e98b42b235fac6cfa302a6471076482a1b26565852ba87f227fa684d465812e4ed8712b131f3838da619e54f0a3f532c5026725c5aa0c9f630ed6c0855584bb0e48e5ead27edc6c0ab18ff0e1b244e2d0c2b455b41a18ce536eb720a074f5df4e09112b63fbe80d8a415f24711381c2c11744b555fbf90a627f47903075b5af5ebc253b220f28fc8ae56e44b18394e275e63485741aa99f22bed7243'
l = len(c)
c = [codecs.decode(c[i*64:i*64+64].strip().encode(), 'hex') for i in range(l//64+1)]
c[-1] += b'\x00' * (32 - len(c[-1]))

msg = np.zeros([len(c), len(c[0])], dtype=int)

getSpace()

dat = sorted(dat)[::-1]
for w, index, pos in dat:
infer(index, pos)

# 这里用自己拿到的数据调
know(0, 0, 'T')
know(0, 5, 'u')
know(1, 11, 'k')
know(0, 12, 'e')
know(0, 23, 'B')
know(0, 29, 'e')

#print('\n'.join([''.join([chr(c) for c in x]) for x in msg]))
m = ''.join([''.join([chr(c) for c in x]) for x in msg])[:l//2]
print(m)
print(md5(m.encode()).hexdigest())