0%

2023-NepCTF-Crypto-WP

Crypto

random_RSA

题目:

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
from gmpy2 import next_prime, invert as inverse_mod
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
from random import getrandbits
from math import lcm
from sys import exit

global_bits = 1024

BANNER = rb"""
.--------.--------.--------.--------.--------.--------.--------.--------.--------.--------.--------.
| N.--. | E.--. | P.--. | C.--. | T.--. | F.--. | H.--. | A.--. | P.--. | P.--. | Y.--. |
| :/\: | (\/) | :(): | :/\: | :/\: | :/\: | (\/) | :(): | :/\: | :/\: | (\/) |
| :\/: | :\/: | ()() | (__) | :\/: | (__) | :\/: | ()() | :\/: | :\/: | :\/: |
| '--'n | '--'e | '--'p | '--'c | '--'t | '--'f | '--'h | '--'a | '--'p | '--'p | '--'y |
`--------`--------`--------`--------'--------`--------`--------`--------`--------`--------`--------`
"""


def generate_prime(bits: int):
p = (getrandbits(bits - 32) << 32)
return next_prime(p)


def generate_private_key(bits: int):
p, q = generate_prime(bits), generate_prime(bits)
n, phi = p * q, lcm(p-1, q - 1)

d = inverse_mod(0x10001, phi)
privateKey = RSA.construct((int(n), int(0x10001), int(d), int(p), int(q)))
return privateKey, p > q


if __name__ == "__main__":
print(BANNER.decode())
print("Welcome to the world of random RSA.")
print("Please make your choice.")
for _ in range(8):
choice = input()

if choice == '1': #重新生成一次密文
p, q = generate_prime(global_bits), generate_prime(global_bits)
N = p*q
d = generate_prime(global_bits-32)
e = inverse_mod(d, (p * p - 1) * (q * q - 1))
print(f"{int(N)}")
print(f"{int(e)}")

elif choice == '2': #用上述函数生成密文
privateKey, signal = generate_private_key(global_bits)
Cipher = PKCS1_v1_5.new(privateKey) #PKCS1_v1_5
c = (Cipher.encrypt(flag.encode()))
print(c)
exit()

else:
exit()

*思路*

观察题目可得,加密flag的时候,q,p的参数都是未知的,而在此之前有一个choice1可以拿到之前生成过的参数,所以就可以确定要用随机数预测的办法去获取,q,p的参数。

简单计算一下可以获取到的位数:(在预测最终参数前,有21次获取随机数的的机会)

((1024-32)2+(1024-32-32))//32 == 92\ 927 = 644 >624

只要在此之前通过公钥(N,e)获取q,p,d,就可以获取到大于624个状态参数,那么就可以稳定预测下一次生成的随机数,但是在题目中q,p的位置是未知的,要获取到正确的顺序需要爆破一下这个位置。

观察生成公钥的办法,可以发现e的生成比较诡异,导致e的位数大很多,简单地推一下式子:

ed=1 \mod(p{2}-1)*(q2-1)\ 展开可得:ed = 1+k*(p{2}-1)*(q2-1)\ ed = 1+k(N{2}-q{2}-p^{2}+1)

估算一下位数:e大概4096位,N为2048位,因子1024位,k的范围通过测试可知小于1024位。

那么可以尝试一下SVP问题求解的办法,在模e下,找到小根,即:

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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#part1:
import itertools
from Crypto.Util.number import *

def small_roots(f, bounds, m=1, d=None):
if not d:
d = f.degree()

R = f.base_ring()
N = R.cardinality()

f /= f.coefficients().pop(0)
f = f.change_ring(ZZ)

G = Sequence([], f.parent())
for i in range(m+1):
base = N^(m-i) * f^i
for shifts in itertools.product(range(d), repeat=f.nvariables()):
g = base * prod(map(power, f.variables(), shifts))
G.append(g)

B, monomials = G.coefficient_matrix()
monomials = vector(monomials)

factors = [monomial(*bounds) for monomial in monomials]
for i, factor in enumerate(factors):
B.rescale_col(i, factor)

B = B.dense_matrix().LLL()

B = B.change_ring(QQ)
for i, factor in enumerate(factors):
B.rescale_col(i, 1/factor)

H = Sequence([], f.parent().change_ring(QQ))
for h in filter(None, B*monomials):
H.append(h)
I = H.ideal()
if I.dimension() == -1:
H.pop()
elif I.dimension() == 0:
roots = []
for root in I.variety(ring=ZZ):
root = tuple(R(root[var]) for var in f.variables())
roots.append(root)
return roots
return []

N=
e=

def findout(N,e):
q,p=var('q p')
PR.<k,x> = PolynomialRing(Zmod(e))
f = 1+k*(N**2)-k*x+k
roots = small_roots(f,(2^1024,2^2049),3,3)
tmp=int(roots[0][1])
print(solve([q*p==N,q*q+p*p==tmp],q,p))
return q,p
q,p=findout(N,e)[0]
#part2:
from Crypto.Util.number import *

e=
q =
p =
n = q*p
d = inverse(e,(q*q-1)*(p*p-1))
print(q,'\n',p,'\n',d)
#算出初始的随机数,算完一个一个填进数据文件里面,手笨+急着拿flag就没写自动脚本了

#part3:爆破解密
import random
from Crypto.Util.number import *
from gmpy2 import next_prime, invert as inverse_mod
from math import lcm

def invert_right(m, l, val=''):
length = 32
mx = 0xffffffff
if val == '':
val = mx
i, res = 0, 0
while i * l < length:
mask = (mx << (length - l) & mx) >> i * l
tmp = m & mask
m = m ^ tmp >> l & val
res += tmp
i += 1
return res

def invert_left(m, l, val):
length = 32
mx = 0xffffffff
i, res = 0, 0
while i * l < length:
mask = (mx >> (length - l) & mx) << i * l
tmp = m & mask
m ^= tmp << l & val
res |= tmp
i += 1
return res

def invert_temper(m):
m = invert_right(m, 18)
m = invert_left(m, 15, 4022730752)
m = invert_left(m, 7, 2636928640)
m = invert_right(m, 11)
return m

def clone_mt(record):
state = [invert_temper(i) for i in record]
gen = random.Random()
gen.setstate((3, tuple(state + [0]), None))
return gen



def qiege(n):
te = []
while n!=0:
te.append(n%pow(2,32))
n = n>>32
return te


def hebing(n):
tmp = 0
for i in range(31):
tmp += n[i]*pow(2,32*(i))
return tmp

def solve_flag(q,p):
q = next_prime(q<<32)
p = next_prime(p<<32)
n = q*p
phi = lcm(p-1, q - 1)
d = inverse(65537,(q-1)*(p-1))
c = b")\xe7\x8b:A\xcd\x16\x8bv\x9e\xab\x9f\x1e\xde\xee\x91g8\x15\x89\x14\xe3\x06\xc6C0\xc2\xad\x95$\xa8Q\x8c\xc9>B)\x8fe6$b\xc1?R\xfa\xa8\xe0<Q\x0c#e\xc3\xf0\x1d\xcb\x9e\x7fl\x13\x00*qV\xdd\xd4\x7f\xc4\xa6'y\xd9E\xa6\xbf\xe8*RAn\xd9xS\x8b\xad\xc2\xca5\xc5\x8d\xad\xd25M\xeeW\xdd\xd3\xcaEN\x01\xe2J\xab\x90'\x19\xb3\xd3Q\xfej|+\xdd\x87\xa6\xf4\r\x90_\xa08sV\t'Y\xda\xed\x8f\xcb\x06^\xb5F\x17\xc2\x1f\x8f\xe8L\xfc\xd3\xea\x02\x18LZ.\xb7*9{\xdc\x0b\x1e\xcb:\xed\xe5\x93MI\x16\xbeZ\xfb\x04RXR\xdd~\x1a\xb2\x01\xa1\x96\xc0W\x00D\x8a\x01&)\x8d=\xed\x0f\x9c\xf2\xf0P}\xa35K\x08\xef\xd1\xe07\x83\xa9F\xc7!E\xbc\xa08.\r\xf5\x89\x16\xad\n\xe3\xed\x99'\xb9\x7f\x12\xe7=ea!\xa6\xf7\xe0\xdd\x00\xadC6B9i\x9e\x1e\x86\x87\xc2i\xd5d\xa2\xcbn"
c = bytes_to_long(c)
m = long_to_bytes(pow(c,d,n))
f2 = open('flag.txt','a')
f2.write(str((m)))
f2.write('\n')
f2.close()

def append_num():
num = []
with open('leak.txt','r') as f:
for i in range(1,22):
num.append(int(f.readline())>>32)
return num

def find_out(t):
test = []
for i in t:
test+=qiege(i)

g = clone_mt(test[:624])

predit= []
for i in range(644):
predit.append(g.getrandbits(32))
flag = []
for i in range(31*2):
flag.append(g.getrandbits(32))
q = (hebing(flag[:31]))
p = (hebing(flag[31:]))
solve_flag(q,p)

num = append_num()

def reverse(k):#传入需要逆转的位数即可,因为担心q,p的位置不对进行一次爆破
global re
re[k],re[k+1] = re[k+1],re[k]

re = num
#手笨,直接一个一个循环试了
for i in range(2):
reverse(0)
for j in range(2):
reverse(3)
for k in range(2):
reverse(6)
for l in range(2):
reverse(9)
for a in range(2):
reverse(12)
for b in range(2):
reverse(15)
for c in range(2):
reverse(18)
find_out(re)

Crib:

题目:

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
from hashlib import sha256
import socketserver
from secret import flag
import signal
import string
import random
import os
from string import ascii_uppercase
from random import shuffle,choice,randint
import os
from pyenigma import *
import pyenigma
from myenigma import myReflector,myrotors
from itertools import product




class Task(socketserver.BaseRequestHandler):
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 send(self, msg, newline=True):
try:
if newline:
msg += b'\n'
self.request.sendall(msg)
except:
pass

def recv(self, prompt=b'[-] '):
self.send(prompt, newline=False)
return self._recvall()

def proof_of_work(self):
random.seed(os.urandom(8))
proof = ''.join(
[random.choice(string.ascii_letters+string.digits) for _ in range(20)])
_hexdigest = sha256(proof.encode()).hexdigest()
self.send(f"[+] sha256(XXXX+{proof[4:]}) == {_hexdigest}".encode())
x = self.recv(prompt=b'[+] Plz tell me XXXX: ')
if len(x) != 4 or sha256(x+proof[4:].encode()).hexdigest() != _hexdigest:
return False
return True

def handle(self):
signal.alarm(60)
if not self.proof_of_work():
self.send(b'[!] Wrong!')
return
key="WETTERBERICHT"
letters=list(ascii_uppercase)
for _ in range(10):
others="".join([choice(letters) for i in range(40)])
pos=randint(0,len(others))
text=others[:pos]+key+others[pos:]
dayrotors=tuple([choice(myrotors) for i in range(3)])
for times in range(20):
tmpkey="".join([choice(letters) for i in range(3)])
shuffle(letters)
plugin=" ".join([letters[2*i]+letters[2*i+1] for i in range(randint(5,10))])
myEnigma=enigma.Enigma(myReflector,*dayrotors,tmpkey,plugin)
ctx=myEnigma.encipher(text)
self.send(ctx.encode())
self.send(b"now give me the pos:")
ans=self.recv()
try:
ans=int(ans)
except:
self.send("wrong pos num")
if ans!=pos:
return

self.send(b'here is your flag')
self.send(flag)


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


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


if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 10002
server = ForkedServer((HOST, PORT), Task)
server.allow_reuse_address = True
print(HOST, PORT)
server.serve_forever()

myEngma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pyenigma import rotor 
from string import ascii_uppercase

def newre(s):
p=[0]*26
ref=ascii_uppercase
for i in range(0,26,2):
p[ref.index(s[i])]=s[i+1]
p[ref.index(s[i+1])]=s[i]
return "".join(p)
check=lambda a,b:len(a)==len(b) and all([i!=j for i,j in zip(a,b)])
checkd=lambda a,b:len(a)==len(b) and all([i!=j and b[a.index(j)]==i and a[b.index(i)]==j for i,j in zip(a,b)])



myReflector=rotor.Reflector('WOEHCKYDMTFRIQBZNLVJXSAUGP',"myReflector")

myrotor1=rotor.Rotor('UHQAOFBEPIKZSXNCWLGJMVRYDT',"A",name="myrotor1")
myrotor2=rotor.Rotor('RIKHFBUJDNCGWSMZVXEQATOLYP',"A",name="myrotor2")
myrotor3=rotor.Rotor('ENQXUJSIVGOMRLHYCDKTPWAFZB',"A",name="myrotor3")
myrotor4=rotor.Rotor('JECGYWNDPQUSXZMKHRLTAVFOIB',"A",name="myrotor4")
myrotor5=rotor.Rotor('EYDBNSFAPJTMGURLOIWCHXQZKV',"A",name="myrotor5")
myrotors=[myrotor1,myrotor2,myrotor3,myrotor4,myrotor5]

剩余的pyenigma可以在github上面找到,故不放出。

Enigma Crack

直接上github梭哈脚本,里面有猜测位置的函数,直接多次调用函数猜测密文就可以获得准确的pos

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
from pwn import*
from pwnlib.util.iters import mbruteforce
import itertools
import string
import hashlib
from Crypto.Util.number import *
import code_breaking

def solve_num():#逐个筛去多余的位置
t = [i for i in range(41)]
for i in data:
tmp = code_breaking.possible_crib_positions(i, "WETTERBERICHT")
for k in t:
if k not in tmp:
t.remove(k)
return t[0]

def recvee():
global data
data = []
for i in range(20):
tmp = io.recvline().strip().decode("utf8")
data.append(tmp)

def proof(io):
io.recvuntil(b"XXXX+")
suffix = io.recv(16).decode("utf8")
io.recvuntil(b"== ")
cipher = io.recvline().strip().decode("utf8")
for i in itertools.product(string.ascii_letters+string.digits, repeat=4):
x = "{}{}{}{}".format(i[0],i[1],i[2],i[3])
proof=hashlib.sha256((x+suffix.format(i[0],i[1],i[2],i[3])).encode()).hexdigest()
if proof == cipher:
break
print(x)
io.sendlineafter(b"XXXX:",x.encode())

io=remote('nepctf.1cepeak.cn',32220)
proof(io)
for _ in range(10):
recvee()
io.recvuntil(b"[-] ")
io.sendline(str(solve_num()).encode())
io.interactive()

bombe-Rejewski-revenge:

题目

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
from hashlib import sha256
import socketserver
from secret import flag
import signal
import string
import random
from string import ascii_uppercase
from random import shuffle,choice
import os
from pyenigma import *
from myenigma import myReflector,myrotors
from itertools import product

class Task(socketserver.BaseRequestHandler):
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 send(self, msg, newline=True):
try:
if newline:
msg += b'\n'
self.request.sendall(msg)
except:
pass

def recv(self, prompt=b'[-] '):
self.send(prompt, newline=False)
return self._recvall()

def proof_of_work(self):
random.seed(os.urandom(8))
proof = ''.join(
[random.choice(string.ascii_letters+string.digits) for _ in range(20)])
_hexdigest = sha256(proof.encode()).hexdigest()
self.send(f"[+] sha256(XXXX+{proof[4:]}) == {_hexdigest}".encode())
x = self.recv(prompt=b'[+] Plz tell me XXXX: ')
if len(x) != 4 or sha256(x+proof[4:].encode()).hexdigest() != _hexdigest:
return False
return True

def handle(self):
if not self.proof_of_work():
self.send(b'[!] Wrong!')
return
signal.alarm(20)
score=0
r1,r2,r3=myrotors[:3]
for _ in range(30):
s=product(ascii_uppercase,repeat=3)
daykey="".join(choice(list(s)))
k1=list(ascii_uppercase)
k2=list(ascii_uppercase)
k3=list(ascii_uppercase)
shuffle(k1)
shuffle(k2)
shuffle(k3)
tmpkeys=[x+y+z for x,y,z in zip(k1,k2,k3)]
shuffle(k3)
plugin="".join([k3[i]+k3[i+1]+' ' for i in range(0,20,2)])

for key in tmpkeys:
myEnigma=enigma.Enigma(myReflector,r1,r2,r3,daykey,plugin)
c1=myEnigma.encipher(key)
c2=myEnigma.encipher(key)
self.send((c1+c2).encode())
self.send(b"now gave me the daykey:")
ans=self.recv()
if daykey.encode()==(ans[:3]):
score+=1

if score>=10:
self.send(b'your score is %d,here is your flag'%score)
self.send(flag)
else :
self.send(b'sorry,your score is %d, plz try again'%score)



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


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


if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 10001
server = ForkedServer((HOST, PORT), Task)
server.allow_reuse_address = True
print(HOST, PORT)
server.serve_forever()

myenigma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pyenigma import rotor 
from string import ascii_uppercase

def newre(s):
p=[0]*26
ref=ascii_uppercase
for i in range(0,26,2):
p[ref.index(s[i])]=s[i+1]
p[ref.index(s[i+1])]=s[i]
return "".join(p)
check=lambda a,b:len(a)==len(b) and all([i!=j for i,j in zip(a,b)])
checkd=lambda a,b:len(a)==len(b) and all([i!=j and b[a.index(j)]==i and a[b.index(i)]==j for i,j in zip(a,b)])



myReflector=rotor.Reflector('WOEHCKYDMTFRIQBZNLVJXSAUGP',"myReflector")

myrotor1=rotor.Rotor('UHQAOFBEPIKZSXNCWLGJMVRYDT',"A",name="myrotor1")
myrotor2=rotor.Rotor('RIKHFBUJDNCGWSMZVXEQATOLYP',"A",name="myrotor2")
myrotor3=rotor.Rotor('ENQXUJSIVGOMRLHYCDKTPWAFZB',"A",name="myrotor3")
myrotor4=rotor.Rotor('JECGYWNDPQUSXZMKHRLTAVFOIB',"A",name="myrotor4")
myrotor5=rotor.Rotor('EYDBNSFAPJTMGURLOIWCHXQZKV',"A",name="myrotor5")
myrotors=[myrotor1,myrotor2,myrotor3,myrotor4,myrotor5]

思路:

目标是获取daily key。题目给出了用daily key加密两次key后的结果,同时提示了一个人名。

打开bilibili:一看就懂的历史小故事

观后可知,如果把每一位加密视作一次函数,通过一定群论的证明,可以知道,在同一日密钥加密对话密钥两次的情况下,我们可以根据字母之间映射的关系构成一个映射环。同一日密钥加密的情况下,一轮字母表所构成的环的个数、环中字母的数量相同,这个特征很像数字指纹,利用这个特征,在本地生成一遍数据库,将数字指纹对应的日密钥保存下来,拿到服务器进行比对就可以获得flag。

生成脚本:

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
import signal
import string
import random
from string import ascii_uppercase
from random import shuffle,choice
import os
from pyenigma import *
from myenigma import myReflector,myrotors
from itertools import *
from pwn import*
from pwnlib.util.iters import mbruteforce
import itertools
import string
import hashlib
from Crypto.Util.number import *

k1=list(ascii_uppercase)
k2=list(ascii_uppercase)
k3=list(ascii_uppercase)
shuffle(k1)
shuffle(k2)
shuffle(k3)

def test(x):#随便拿服务器生成的脚本去生成数字指纹
r1,r2,r3=myrotors[:3]
data = []
daykey = x
tmpkeys=[x+y+z for x,y,z in zip(k1,k2,k3)]
plugin="".join([k3[i]+k3[i+1]+' ' for i in range(0,20,2)])
for key in tmpkeys:
myEnigma=enigma.Enigma(myReflector,r1,r2,r3,daykey,plugin)
c1=myEnigma.encipher(key+key)
data.append(c1)
return data

def search(tmp,data):#搜索字符串映射
for i in data:
if i[0] == tmp:
return i[1]

def find_out(tmp,data):#寻找密文中单个映射环
ring = ''
ring += tmp
while True:
tmp = search(tmp,data)
if tmp != ring[0]:
ring+=tmp
else:
return (ring)

def makering(data):#寻找密文中所有的映射环
rings = []
for i in data:
flag = 0
for k in rings:
if i[0] in k:
flag = 1
if flag == 0:
rings.append(find_out(i[0],data))
rings = sorted(rings,key=lambda x:len(x))
return rings

def makeconfig(rings):#生成数字指纹
'''len(ring)+ring_num'''
proof = ''
proof += str(len(rings))
for i in rings:
proof+=str(len(i))
return proof

def qiege(text):
t1,t2,t3=[],[],[]
for i in text:
t1.append(i[0]+i[3])
t2.append(i[1]+i[4])
t3.append(i[2]+i[5])
return t1,t2,t3

f = open('exp.txt','w') #生成数据文件
for i in product(ascii_uppercase, repeat=3):
x = "{}{}{}".format(i[0],i[1],i[2])
new_data = test(x)
x1,x2,x3 = qiege(new_data)
num_data1 = makeconfig(makering(x1))
num_data2 = makeconfig(makering(x2))
num_data3 = makeconfig(makering(x3))
total2 = num_data1+num_data2+num_data3
f.write(total2+' '+x+'\n')
f.close()

去服务器比对数据:

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
import signal
import string
from itertools import *
from pwn import*
from pwnlib.util.iters import mbruteforce
import itertools
import string
import hashlib
from Crypto.Util.number import *

def search(tmp,data):
for i in data:
if i[0] == tmp:
return i[1]

def find_out(tmp,data):
ring = ''
ring += tmp
count = 0
while True:
if count >26:
print('bad')
return None

tmp = search(tmp,data)
if tmp != ring[0]:
ring+=tmp
else:
return (ring)
count += 1

def makering(data):
rings = []
for i in data:
flag = 0
for k in rings:
if i[0] in k:
flag = 1
if flag == 0:
rings.append(find_out(i[0],data))
rings = sorted(rings,key=lambda x:len(x))
return rings

def makeconfig(rings):
'''len(ring)+ring_num'''
proof = ''
proof += str(len(rings))
for i in rings:
proof+=str(len(i))
return proof


def qiege(text):
t1,t2,t3=[],[],[]
for i in text:
t1.append(i[0]+i[3])
t2.append(i[1]+i[4])
t3.append(i[2]+i[5])
return t1,t2,t3

def solve_num(text):
t1,t2,t3 = qiege(text)
num_test1 = makeconfig(makering(t1))
num_test2 = makeconfig(makering(t2))
num_test3 = makeconfig(makering(t3))
total1 = num_test1+num_test2+num_test3
f = open('exp.txt','r')
while True:
new_data = f.readline()
new_data = new_data.replace('\n','')
new_data = new_data.split(' ')
total2 = new_data[0]
if total2 == total1:
x = new_data[1]
print(x)
break
return x

def recvee():
global text
text = []
for i in range(26):
tmp = io.recvline().strip().decode("utf8")
text.append(tmp)

def proof(io):
io.recvuntil(b"XXXX+")
suffix = io.recv(16).decode("utf8")
io.recvuntil(b"== ")
cipher = io.recvline().strip().decode("utf8")
for i in itertools.product(string.ascii_letters+string.digits, repeat=4):
x = "{}{}{}{}".format(i[0],i[1],i[2],i[3])
proof=hashlib.sha256((x+suffix.format(i[0],i[1],i[2],i[3])).encode()).hexdigest()
if proof == cipher:
break
print(x)
io.sendlineafter(b"XXXX:",x.encode())



io=remote('nepctf.1cepeak.cn',31094)
proof(io)
for _ in range(30):
recvee()
io.recvuntil(b"[-] ")
io.sendline(str(solve_num(text)).encode())

io.interactive()
#NepCTF{f8cc69c6-eaa4-4cbd-867c-4aa8440e3ad7}

Recover:

题目:

1
暂时找不到了:(

思路:仔细观察P盒的特征,结合异或运算就是模2下的加法运算这个性质,整个加密都是线性的,所以可以通过以下关系式构造解出原文。(其中可以试一下发现原文头是固定的,也就是说可以使用已知明文攻击去解密原文)

E = P(P(P(M)+B_1)+B_2)+B_3\ E = P{3}M+P{2}B_1+PB_2+B_3\ T = P^{2}B_1+PB_2+B_3\ E = P^{3}M+T\

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
135
136
from Crypto.Util.number import *
P = [[0, 2, 3, 4, 5, 6],
[1, 4],
[0, 3],
[0, 3, 4, 5],
[0, 1, 2, 3, 4, 7],
[2, 3, 4, 5, 6],
[0, 1, 2, 3],
[1, 2, 3, 4, 5, 7],
[8, 12, 13, 14],
[8, 11, 12, 13, 15],
[9, 12, 15],
[11, 13, 15],
[8, 9, 10, 12, 13, 15],
[8, 9],
[11, 13, 14],
[10],
[16, 19, 20],
[16, 18, 19, 20, 21],
[16, 17, 18, 19],
[17, 18, 20, 22, 23],
[17, 19, 23],
[17, 18, 20, 21, 23],
[16, 18, 20, 21, 22, 23],
[16, 17, 18, 20, 21, 22, 23],
[25, 26, 29, 31],
[25, 26],
[26, 28, 30],
[27, 28, 29, 30, 31],
[25, 29],
[25, 26, 30, 31],
[28, 29, 30, 31],
[24, 26, 29, 31],
[35, 36, 39],
[33, 35, 38],
[33, 35, 37, 39],
[32, 33, 34, 35, 37, 38],
[32, 33, 34, 35, 37, 39],
[35, 38],
[33, 34, 38, 39],
[33, 34, 39],
[41, 42, 43, 44, 47],
[40, 41, 42, 45, 47],
[41, 42, 45, 47],
[40, 43, 44, 46],
[41, 46, 47],
[41, 42, 43, 44, 46, 47],
[41, 42, 44, 45],
[40, 41, 42, 45, 46],
[48, 50, 51, 52, 53, 54, 55],
[48, 49, 50, 52, 53, 54, 55],
[49, 55],
[48, 49, 50, 51, 52, 54],
[52, 53],
[48, 49, 53, 54, 55],
[48, 49, 52, 55],
[48, 49, 51, 52, 55],
[58, 59],
[56, 61, 63],
[57, 63],
[56, 59, 60],
[61, 63],
[57, 58, 61, 62, 63],
[57, 58],
[60, 62]]
A = [[0 for _ in range(64)] for i in range(64)]
count = 0
for i in P:
for k in i:
A[count][k] =1
count+=1
ms = MatrixSpace(GF(2), 64, 64)
P = ms(A)

def trans(data):
vs = MatrixSpace(GF(2), 64,1)
v = vs(data)
return v

def Martix2str(T):
M=T.str().replace('\n','').replace('[','').replace(']','')
return M

def enc_text(v,keys):
t = [int(i) for i in v]
tmp = trans(t)
for i in keys:
tmp = P*tmp+trans(i)
return Martix2str(tmp)

def dec(v,keys):
t = [int(i) for i in v]
tmp = trans(t)
keys = keys[::-1]
for i in keys:
tmp = P.inverse()*(tmp-trans(i))
return Martix2str(tmp)

head = '0000000000000000000000000000000000000000000000000110011001101100'
enc = '11101100100000110101100101100001100111011100100111000000010110000110011011000100110101110111010000100100001100010011001100010100101000110001011101000000100010101000000110000110011110001101110110110111000000100010011011011011101011101000000000100010000101001110100101011000001110010000000000100110001101110011111010001100101101111010101111101110100110101010011010011010101110100001001101100110010000010000011100100101111010010000011001000110000100110111100010101011000100100111010000101010110110001010110101111111'

def test(E,M):
E = trans([int(i) for i in E])
M = trans([int(i) for i in M])
X = P.inverse()*P.inverse()*P.inverse()*E - M
return X

X = test(enc[0:64],head)

def test2(E,M):
E = trans([int(i) for i in E])
M = trans([int(i) for i in M])
T = E - P*P*P*M
return Martix2str(T)

def solve_num(E,X):
E = trans([int(i) for i in E])
T = P.inverse()*P.inverse()*P.inverse()*E - X
return Martix2str(T)

def makekey(key):
keys = []
for i in range(len(key)//8):
l = bytes_to_long(key[i*8:i*8+8])
m = bin(l)[2:].zfill(8*8)
keys.append([int(i) for i in m])
return keys



flag = ''
for i in range(8):
flag += solve_num(enc[i*64:(i+1)*64],X)
#print(Martix2str(X))
print(long_to_bytes(eval('0b'+flag)))
#b'flag{flag_is_the_readable_key_whose_md5_starts_with_3fe04}'

然后就是最烧脑的一步了,解出原文发现,真正的flag藏在密钥里,要是直接爆破穷举密钥的话有上亿种可能,也跑不了,要考虑削减复杂度。经过尝试可以发现密钥的第一部分可以随便构造,既然说密钥是可读的,那就用"flag{"这个头去爆破剩下三位试试。

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
import itertools
from Crypto.Util.number import *
import string
P_inv = P
X = '1010001101000011011011110101111111101100011111100101100100110101'
T = '1110110010000011010110010110000110011101110010011000001010000010'
v = MatrixSpace(GF(2),8,8)
k1=b'flag{'
k2=b''
k3=b''

def trans(data):
vs = MatrixSpace(GF(2), 8,1)
v = vs(data)
return v

def make(n):
tmp = []
for i in range(8):
tmp += P.inverse()[i+(n*8)][n*8:(n+1)*8]
tmp = v(tmp)
return tmp


for i in range(5):
test = X[8*i:8*(i+1)]
test = [int(j) for j in test]
test = trans(test)
P_new = make(i)
for l in itertools.product(string.ascii_letters+string.digits+'{_?!}', repeat=2):
k2_tmp,k3_tmp=bytes_to_long(l[0].encode()),bytes_to_long(l[1].encode())
k1_tmp = k1[i]
k1_tmp,k2_tmp,k3_tmp=bin(k1_tmp)[2:].zfill(8),bin(k2_tmp)[2:].zfill(8),bin(k3_tmp)[2:].zfill(8)
k1_tmp = trans([int(j) for j in k1_tmp])
k2_tmp = trans([int(j) for j in k2_tmp])
k3_tmp = trans([int(j) for j in k3_tmp])

proof = P_new*P_new*k1_tmp + P_new*k2_tmp + k3_tmp
if proof == test:
k2 += l[0].encode()+str(i).encode()
k3 += l[1].encode()+str(i).encode()

for i in range(5,5+3):
test = X[8*i:8*(i+1)]
test = [int(j) for j in test]
test = trans(test)
P_new = make(i)
for l in itertools.product(string.ascii_letters+string.digits+'{_?!}~*', repeat=3):
k1_tmp,k2_tmp,k3_tmp=bytes_to_long(l[0].encode()),bytes_to_long(l[1].encode()),bytes_to_long(l[2].encode())
k1_tmp,k2_tmp,k3_tmp=bin(k1_tmp)[2:].zfill(8),bin(k2_tmp)[2:].zfill(8),bin(k3_tmp)[2:].zfill(8)
k1_tmp = trans([int(j) for j in k1_tmp])
k2_tmp = trans([int(j) for j in k2_tmp])
k3_tmp = trans([int(j) for j in k3_tmp])

proof = P_new*P_new*k1_tmp + P_new*k2_tmp + k3_tmp
if proof == test:
k1 += l[0].encode()+str(i).encode()
k2 += l[1].encode()+str(i).encode()
k3 += l[2].encode()+str(i).encode()

print(k1)
print(k2)
print(k3)

从中硬搜查找爆破试试吧,肉眼解题了属于是,最后看了别人WP发现真的是神仙过海各显神通了,

最终key: flag{hardtorecoverkey}

AggServer:

题目:

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
# !/usr/bin/env python
from Crypto.Util.number import *
from hashlib import sha256
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from base64 import b64encode
from FLAG import flag
from AggServer import AggServer
import socketserver
import os, sys, random
import signal, string

ROUNDS=20
BANNER=br'''
Welcome to my Secure Aggregation System.
If you can pass my 20 rounds of agg testing, I'll give you flag~
Have fun!
'''


class Task(socketserver.BaseRequestHandler):
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 send(self, msg, newline=True):
try:
if newline:
msg += b'\n'
self.request.sendall(msg)
except:
pass

def recv(self, prompt=b'> '):
self.send(prompt, newline=False)
return self._recvall()

def close(self):
self.request.close()

def handle(self):
signal.alarm(180)

self.send(BANNER)
self.send(b"Generating parameters...")
agg=AggServer(114)
agg.genKeys()
self.send(agg.system_info().encode())

try:
for i in range(ROUNDS):
self.send(f'#Round {i+1}'.encode())
enc_list=agg.get_enc_list()
self.send(f"enc_list={enc_list}".encode())
key=agg.aggregate()



message=''.join(random.sample(string.ascii_letters,16))
aes_key=sha256(str(key).encode()).digest()[:16]
aes=AES.new(aes_key,AES.MODE_CBC,iv=bytes(range(16)))
enc=aes.encrypt(pad(message.encode(),16))
self.send(f"enc={b64encode(enc)}".encode())
inp=self.recv(b"input your message: ").decode()
if inp==message:
self.send(b'Correct!')
else:
self.send(b'Error!')
exit(0)
agg.update()
self.send(flag)
except:
self.send(b'wtf?')
self.close()


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


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


if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 12345
server = ForkedServer((HOST, PORT), Task)
server.allow_reuse_address = True
server.serve_forever()

AggServer:

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
from Crypto.Util.number import *
from User import User

class AggServer:

def __init__(self,mbits):#114
self.N=8
self.g=2
self.p=0x9f785dd75d97d7dea66a89a038e7c880b962e526fa9d0f14639de8d82b953fbf0e01739495df3d5fdb189aa079c70e9cc49c7390c2fd3166d91fe8b511f918a7
self.mbits=mbits
self.users=[ User(mbits,i) for i in range(self.N)] #8个用户
self.M=getPrime(mbits+self.N.bit_length()+1) #118
self.params=(self.g,self.p)

def genKeys(self):
for u in self.users:
u.genKey(self.params)
for u in self.users:
u.agree(self.users,self.params[1])

def get_enc_list(self):
enc_list=[]
for u in self.users:
enc_data=u.get_enc_data(self.M)
enc_list.append(enc_data)
return enc_list

def aggregate(self):
self.key=sum([ u.data for u in self.users])%self.M
return self.key

def update(self):
for u in self.users:
u.update_data(self.key)

def system_info(self):
info = ""
info += "System params: \n"
info += f"g={self.params[0]}\np={self.params[1]}\nM={self.M}\n"
info += "User pubkeys: \n"
info += f"pubkeys={str([ u.pub for u in self.users])}\n"
return info

user:

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
from Crypto.Util.number import *

class PRG:
def __init__(self,seed):
self.state=seed
self.a=114514
self.b=1919810
self.kbits=seed.bit_length()
self.bits=[]

def gen(self):
self.state=(self.a*self.state+self.b)&((1<<self.kbits)-1)
bin_arr=list(map(int,bin(self.state)[2:].zfill(self.kbits)[::-1]))
self.bits.extend(bin_arr)

def randbits(self,n):
while len(self.bits)<n:
self.gen()
output=self.bits[:n]
self.bits=self.bits[n:]
return int(''.join(map(str,output)),2)


class User:

def __init__(self,mbits,id):
self.mbits=mbits
self.id=id
self.data=getRandomNBitInteger(mbits)

def genKey(self,params):
g,p=params
self.priv=getRandomRange(1,p)
self.pub=pow(g,self.priv,p)

def agree(self,users,p):
self.agreement_keys={}
for u in users:
if u.id!=self.id:
u_pub=u.pub
k=pow(u_pub,self.priv,p)
self.agreement_keys.update({u.id:k})

def get_enc_data(self,M):
enc=(114*self.data+514)%M
for id,k in self.agreement_keys.items():
if id>self.id:
enc+=PRG(k).randbits(self.mbits)%M
elif id<self.id:
enc-=PRG(k).randbits(self.mbits)%M
return enc

def update_data(self, key):
mask=(1<<self.mbits)-1
noise=getRandomNBitInteger(16)
self.data=(self.data ^ key ^ noise) & mask

代码审计题,给年幼无知的我带来了巨大的伤害,暂时就没做了,以后有空再更新吧(