DGZ's Blog.

hgame2020 not_One-time

Word count: 1.1kReading time: 5 min
2020/03/03 Share

记过的东西都至少会有一点印象,但是看看而已就真的很容易忘。
参考博客:https://blog.csdn.net/wh201906/article/details/104081772/
以及官方wp

题目:
In cryptography, the one-time pad (OTP) is an encryption technique that cannot be cracked, but…
Just XOR ;P
nc 47.98.192.231 25001
hint: reduced key space

以及task.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os, random
import string, binascii, base64

from secret import flag
assert flag.startswith(b'hgame{') and flag.endswith(b'}')

flag_len = len(flag)

def xor(s1, s2):
#assert len(s1)==len(s2)
return bytes( map( (lambda x: x[0]^x[1]), zip(s1, s2) ) )

random.seed( os.urandom(8) )
keystream = ''.join( [ random.choice(string.ascii_letters+string.digits) for _ in range(flag_len) ] )
keystream = keystream.encode()
print( base64.b64encode(xor(flag, keystream)).decode() )

nc端口,会返回一串base64字符串
其中

1
2
3
4
5
6
7
8
9
10
import os, random
import string, binascii, base64
a=string.ascii_letters+string.digits
print (a)#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 即字母和数字

a=base64.b64encode(xor(flag, keystream)).decode()
b=base64.b64decode(a)#假设得到的字符串是Cy1VWg4XIgs3IEgbFkAAUnd3Cz5dQElqKBELEjoKX1RrG1oRGEUobjRcJA==
print (b) #b'9\x10W]\x08B\x05eGK]4*{\x1d\x05~cWWPBg\x7f*\x18}\x0c&5\\\x1c*RE\x00`g B+R?'
print (binascii.hexlify(b'9\x10W]\x08B\x05eGK]4*{\x1d\x05~cWWPBg\x7f*\x18}\x0c&5\\\x1c*RE\x00`g B+R?'))#b'3910575d08420565474b5d342a7b1d057e6357575042677f2a187d0c26355c1c2a524500606720422b523f'
#有86位十六进制数,所以知道flag位数为43

urandom这个函数目前比较安全(我的认识),所以预测随机数不太行

在从“reduced key space”角度多次进行搜索无果后,我尝试从题目“one time pad”的角度进行搜索,发现OTP极其安全。。。这题没法做了
然而,在搜索过程中接触到MTP(Many Time Pad),通过进一步搜索,找到了一篇文章讲解相关内容,发现文中提到的Many Time Pad与本题有些类似。文中题目是若干明文由相同key加密得到若干密文,根据密文求key,并通过key解密目标密文;而本题是flag被随机key加密,需要解出flag。看上去两者所求不同,但是由于加密算法当中的异或运算满足交换律,本题也可看作是明文(随机key)经相同key(flag)加密得到一系列密文,求key(flag)。
继续研究文章,得到了几条重要信息
1.若a ^ x=b,则b ^ x=a(异或的自反性)
2.设明文为m1,m2,密文为c1,c2,则有c1 ^ c2=(m1 ^ key) ^ (m2 ^ key)=m1 ^ key ^ key ^ m2=m1 ^ m2
3.文章中推论得当c1 ^ c2为有意义的英文字母时,对应的m1,m2很可能一个是英文字母一个是空格
第三条概括下来就是,通过寻找有意义的c1 ^ c2来获得m1,m2可能的解

我们的密文是明文与key异或得到的,既然key是urandom生成的,那就是可以任意构造,看作明文。

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
# # -*- coding: utf-8 -*-  

import base64, string
from socket import socket

table = (string.ascii_letters + string.digits).encode() # 源于题目中随机key
# result = (string.ascii_letters + string.digits + '''{}_''').encode() #坑1
result = (string.printable).encode()

ciphers = []
for i in range(30): # 设定获取密文数量
sock = socket()
sock.connect(('47.98.192.231', 25001))
text=sock.recv(500)
ciphers.append(text)
sock.close()

for i in range(len(base64.b64decode(ciphers.pop(0)))):
lis=set()
for cur1 in ciphers:
for cur2 in ciphers:
cy1 = base64.b64decode(cur1)
cy2 = base64.b64decode(cur2)
if (cy1 == cy2):
continue
res=set()
tmp1 = cy1[i] ^ cy2[i]
for j in range(len(table)):
for k in range(len(table)):
if (j == k):
continue
tmp2 = table[j] ^ table[k]
if (tmp1 == tmp2): # 使c1^c2有意义
res1 = int(cy2[i] ^ table[j]).to_bytes(1, 'big')
res2 = int(cy2[i] ^ table[k]).to_bytes(1, 'big')
if (result.find(res1) != -1): # 缩小解范围
res.add(res1)
if (result.find(res2) != -1): # 坑2
res.add(res2)
if (lis == set()):
lis = res
if (res != set()):
tmp = lis.intersection(res)
if (tmp != set()):
lis.intersection_update(res)
else:
lis.update(res)
print(i,''.encode().join(lis))

至于官方wp
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import string, binascii, base64
from socket import socket

keySpace = string.ascii_letters + string.digits

count = 0
def enc():
global count
count += 1
sock = socket()
sock.connect(('127.0.0.1', 1234))
return base64.b64decode( sock.recv(1024) )

LEN = len(enc())

flag = [set(b'h'), set(b'g'), set(b'a'), set(b'm'), set(b'e'), set(b'{'), \
*([set(string.printable.encode()) for _ in range(LEN-7)]), \
set(b'}')]

def check():
return sum(map(len, flag))==LEN

def guess(cipher):
return bytes([ cipher^k for k in keySpace.encode() ])

def update(index, s):
global flag
flag[index] = flag[index] & set(s)

while check()!=True:
cipher = enc()
for i in range(LEN):
m_i = guess(cipher[i])
update(i, m_i)

print( count, 'flag:', ''.join( map(lambda x: chr(next(iter(x))), flag) ) )

CATALOG