de1ctf2019 crypto writeup

Xorz

Brute_by_step

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
from itertools import cycle


def getCipher():
c = "49380d773440222d1b421b3060380c3f403c3844791b202651306721135b6229294a3c3222357e766b2f15561b35305e3c3b670e49382c295c6c170553577d3a2b791470406318315d753f03637f2b614a4f2e1c4f21027e227a4122757b446037786a7b0e37635024246d60136f7802543e4d36265c3e035a725c6322700d626b345d1d6464283a016f35714d434124281b607d315f66212d671428026a4f4f79657e34153f3467097e4e135f187a21767f02125b375563517a3742597b6c394e78742c4a725069606576777c314429264f6e330d7530453f22537f5e3034560d22146831456b1b72725f30676d0d5c71617d48753e26667e2f7a334c731c22630a242c7140457a42324629064441036c7e646208630e745531436b7c51743a36674c4f352a5575407b767a5c747176016c0676386e403a2b42356a727a04662b4446375f36265f3f124b724c6e346544706277641025063420016629225b43432428036f29341a2338627c47650b264c477c653a67043e6766152a485c7f33617264780656537e5468143f305f4537722352303c3d4379043d69797e6f3922527b24536e310d653d4c33696c635474637d0326516f745e610d773340306621105a7361654e3e392970687c2e335f3015677d4b3a724a4659767c2f5b7c16055a126820306c14315d6b59224a27311f747f336f4d5974321a22507b22705a226c6d446a37375761423a2b5c29247163046d7e47032244377508300751727126326f117f7a38670c2b23203d4f27046a5c5e1532601126292f577776606f0c6d0126474b2a73737a41316362146e581d7c1228717664091c"
codeintlist = []
codeintlist.extend(
(map(lambda i: int(c[i:i + 2], 16), range(0, len(c), 2))))
salt = "WeAreDe1taTeam"
si = cycle(salt)
newcodeintlist = [ci ^ ord(next(si)) for ci in codeintlist]
return newcodeintlist


def getKeyPool(cipher, stepSet, plainSet, keySet):
''' 传入的密文串、明文字符集、密钥字符集、密钥长度范围均作为数字列表处理.形如[0x11,0x22,0x33]
返回一个字典,以可能的密钥长度为键,以对应的每一字节的密钥字符集构成的列表为值,密钥字符集为数字列表。
形如{
1:[[0x11]],
3:[
[0x11,0x33,0x46],
[0x22,0x58],
[0x33]
]
}
'''
keyPool = dict()
for step in stepSet:
maybe = [None] * step
for pos in range(step):
maybe[pos] = []
for k in keySet:
flag = 1
for c in cipher[pos::step]:
if c ^ k not in plainSet:
flag = 0
if flag:
maybe[pos].append(k)
for posPool in maybe:
if len(posPool) == 0:
maybe = []
break
if len(maybe) != 0:
keyPool[step] = maybe
return keyPool


def calCorrelation(cpool):
'''传入字典,形如{'e':2,'p':3}
返回可能性,0~1,值越大可能性越大
(correlation between the decrypted column letter frequencies and
the relative letter frequencies for normal English text)
'''
frequencies = {"e": 0.12702, "t": 0.09056, "a": 0.08167, "o": 0.07507, "i": 0.06966,
"n": 0.06749, "s": 0.06327, "h": 0.06094, "r": 0.05987, "d": 0.04253,
"l": 0.04025, "c": 0.02782, "u": 0.02758, "m": 0.02406, "w": 0.02360,
"f": 0.02228, "g": 0.02015, "y": 0.01974, "p": 0.01929, "b": 0.01492,
"v": 0.00978, "k": 0.00772, "j": 0.00153, "x": 0.00150, "q": 0.00095,
"z": 0.00074}
relative = 0.0
total = 0
fpool = 'etaoinshrdlcumwfgypbvkjxqz'
total = sum(cpool.values()) # 总和应包括字母和其他可见字符
for i in cpool.keys():
if i in fpool:
relative += frequencies[i] * cpool[i] / total
return relative


def analyseFrequency(cfreq):
key = []
for posFreq in cfreq:
mostRelative = 0
for keyChr in posFreq.keys():
r = calCorrelation(posFreq[keyChr])
if r > mostRelative:
mostRelative = r
keychar = keyChr
key.append(keychar)

return key


def getFrequency(cipher, keyPoolList):
''' 传入的密文作为数字列表处理
传入密钥的字符集应为列表,依次包含各字节字符集。
形如[[0x11,0x12],[0x22]]
返回字频列表,依次为各字节字符集中每一字符作为密钥组成部分时对应的明文字频
形如[{
0x11:{'a':2,'b':3},
0x12:{'e':6}
},
{
0x22:{'g':1}
}]
'''
freqList = []
keyLen = len(keyPoolList)
for i in range(keyLen):
posFreq = dict()
for k in keyPoolList[i]:
posFreq[k] = dict()
for c in cipher[i::keyLen]:
p = chr(k ^ c)
posFreq[k][p] = posFreq[k][p] + 1 if p in posFreq[k] else 1
freqList.append(posFreq)
return freqList


def vigenereDecrypt(cipher, key):
plain = ''
cur = 0
ll = len(key)
for c in cipher:
plain += chr(c ^ key[cur])
cur = (cur + 1) % ll
return plain


ps = []
ks = []
ss = []
ps.extend(range(32, 127))
ks.extend(range(0xff + 1))
ss.extend(range(38))
cipher = getCipher()

keyPool = getKeyPool(cipher=cipher, stepSet=ss, plainSet=ps, keySet=ks)
for i in keyPool:
freq = getFrequency(cipher, keyPool[i])
key = analyseFrequency(freq)
# plain = vigenereDecrypt(cipher, key)
# print(plain, "\n")
print(''.join(map(chr, key)))

image-20200508174922629

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
79
80
81
82
import base64
import string


def bxor(a, b): # xor two byte strings of different lengths
if len(a) > len(b):
return bytes([x ^ y for x, y in zip(a[:len(b)], b)])
else:
return bytes([x ^ y for x, y in zip(a, b[:len(a)])])


def hamming_distance(b1, b2):
differing_bits = 0
for byte in bxor(b1, b2):
differing_bits += bin(byte).count("1")
return differing_bits


def break_single_key_xor(text):
key = 0
possible_space = 0
max_possible = 0
letters = string.ascii_letters.encode('ascii')
for a in range(0, len(text)):
maxpossible = 0
for b in range(0, len(text)):
if(a == b):
continue
c = text[a] ^ text[b]
if c not in letters and c != 0:
continue
maxpossible += 1
if maxpossible > max_possible:
max_possible = maxpossible
possible_space = a
key = text[possible_space] ^ 0x20
return chr(key)


no_salt = bytes.fromhex('1e5d4c055104471c6f234f5501555b5a014e5d001c2a54470555064c443e235b4c0e590356542a130a4242335a47551a590a136f1d5d4d440b0956773613180b5f184015210e4f541c075a47064e5f001e2a4f711844430c473e2413011a100556153d1e4f45061441151901470a196f035b0c4443185b322e130806431d5a072a46385901555c5b550a541c1a2600564d5f054c453e32444c0a434d43182a0b1c540a55415a550a5e1b0f613a5c1f10021e56773a5a0206100852063c4a18581a1d15411d17111b052113460850104c472239564c0755015a13271e0a55553b5a47551a54010e2a06130b5506005a393013180c100f52072a4a1b5e1b165d50064e411d0521111f235f114c47362447094f10035c066f19025402191915110b4206182a544702100109133e394505175509671b6f0b01484e06505b061b50034a2911521e44431b5a233f13180b5508131523050154403740415503484f0c2602564d470a18407b775d031110004a54290319544e06505b060b424f092e1a770443101952333213030d554d551b2006064206555d50141c454f0c3d1b5e4d43061e453e39544c17580856581802001102105443101d111a043c03521455074c473f3213000a5b085d113c194f5e08555415180f5f433e270d131d420c1957773f560d11440d40543c060e470b55545b114e470e193c155f4d47110947343f13180c100f565a000403484e184c15050250081f2a54470545104c5536251325435302461a3b4a02484e12545c1b4265070b3b5440055543185b36231301025b084054220f4f42071b1554020f430b196f19564d4002055d79')

b = no_salt

normalized_distances = []

for KEYSIZE in range(2, 40):
b1 = b[: KEYSIZE]
b2 = b[KEYSIZE: KEYSIZE * 2]
b3 = b[KEYSIZE * 2: KEYSIZE * 3]
b4 = b[KEYSIZE * 3: KEYSIZE * 4]
b5 = b[KEYSIZE * 4: KEYSIZE * 5]
b6 = b[KEYSIZE * 5: KEYSIZE * 6]

normalized_distance = float(
hamming_distance(b1, b2) +
hamming_distance(b2, b3) +
hamming_distance(b3, b4) +
hamming_distance(b4, b5) +
hamming_distance(b5, b6)
) / (KEYSIZE * 5)
normalized_distances.append(
(KEYSIZE, normalized_distance)
)
normalized_distances = sorted(normalized_distances, key=lambda x: x[1])


for KEYSIZE, _ in normalized_distances:
block_bytes = [[] for _ in range(KEYSIZE)]
for i, byte in enumerate(b):
block_bytes[i % KEYSIZE].append(byte)
keys = ''
try:
for bbytes in block_bytes:
keys += break_single_key_xor(bbytes)
key = bytearray(keys * len(b), "utf-8")
plaintext = bxor(b, key)
print("size:" + "%-2d" % KEYSIZE, end='')
print(" key :", keys, "n")
# s = bytes.decode(plaintext)
# print(s)
except Exception:
continue

记得去盐

image-20200508175543010

评论