[题目考点]

  • DH-MIMT
  • RSA私钥低位泄露引起的CopperSmith攻击 (预期解是解同余方程)

[题目文件]

Click Here to Download

[题解分析]

Encryption

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Step 1
aes = AES.new(key, AES.MODE_ECB)
flag = adaptmessage(flag) # right padding with b'\x00'
encrypted_flag = b64encode(aes.encrypt(flag))
# Step 2
p = getPrime(700)
q = getPrime(1400)
e = getPrime(10)
assert(GCD(e, phi) == 1)
enc = pow(bytes_to_long(key), e, n)
print("Brooke's partial d: " + str(d % (2**1050))) # leak d's low 1050bits
# Step 3
# DH-MIMT challenging(5 round's success required)
# Finally given message including enc in Step2

Decryption

DH-MIMT后,获得AES密钥的对应RSA密文.

  • Crack p (700 bits)

$\because ed_{0}p\equiv p+kpn-kp^{2}-kn+kp(mod\ 2^{size(d_{0})}),k\in [1,e]$

$\because size(p)<size(d_{0})$

$\therefore$ 求解p的一元模等式即可(至多e次)

  • Crack q (1400 bits)

$\because size(n)=2100,size(d0)=1050>\frac{size(n)}{4}$

由Crack p里提到的模等式可求解出q的低1050 bits,因此问题转化为partial_p

$\beta$取0.65略小于$\frac{size(q)}{size(n)}$,根上界取$2^{1400-size(q_{0})}$.

  • 在Crack p时每一轮start一个守护线程,来进行Crack q

[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
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
import threading
from pwn import *
from base64 import b64decode
from Crypto.Cipher import AES
from Crypto.Util.number import *

p = 217534615279223294476101434763509239207
g = 2
state = False
# global N_factor
q = None
io = remote("121.40.247.133", "28419")


def adapt_message(message):
return message.ljust(16 - len(message) % 16 + len(message), b'\x00')


def get_encrypted_flag():
print("[++++++++++++++++] Enc_flag generating [++++++++++++++++]")
io.recvuntil("encrypted flag: b'")
encrypted_flag = b64decode(io.recvuntil("'"))
print("Enc_flag:", encrypted_flag)
print("[++++++++++++++++] Enc_flag generating completed [++++++++++++++++]")
return encrypted_flag


def gen_RSA_key():
print("[++++++++++++++++] RSA key generating [++++++++++++++++]")
io.recvuntil("e = ")
e = int(io.recvline())
print("e:", str(e))
io.recvuntil("n = ")
n = int(io.recvline())
print("n:", str(n))
io.recvuntil("partial d: ")
d0 = int(io.recvline()) #low 1050bits
print("d0:", str(d0))
print("[++++++++++++++++] RSA key generating completed [++++++++++++++++]")
return (e, n, d0)


def MITM_connect():
print("[++++++++++++++++] MITM connecting [++++++++++++++++]")
gamma = 1551
keyG = pow(g, gamma, p)
io.recvuntil("A: ")
keyA = int(io.recvline())
print(keyA)
io.sendlineafter("A: ", str(keyG))
io.recvuntil("B: ")
keyB = int(io.recvline())
io.sendlineafter("B: ", str(keyG))
keyC = pow(keyB, gamma, p) #BG通信
keyS = pow(keyA, gamma, p) #AG通信
keyC = long_to_bytes(keyC).rjust(16, b'\x00')
keyS = long_to_bytes(keyS).rjust(16, b'\x00')
cipherC = AES.new(keyC, AES.MODE_ECB)
cipherS = AES.new(keyS, AES.MODE_ECB)
print("keyC:", keyC)
print("keyS:", keyS)
print("[++++++++++++++++] MITM connecting completed [++++++++++++++++]")
return (cipherC, cipherS)


def MITM_conversation(cipherC, cipherS):
print("[++++++++++++++++] MITM conversation [++++++++++++++++]")
for i in range(5):
io.recvuntil("A: ")
messageA = io.recvline().strip()
messageA = long_to_bytes(int(messageA, 16))
messageA = cipherS.decrypt(messageA.rjust((16 - len(messageA) % 16) % 16 + len(messageA), b'\x00'))
print("A:", messageA)
randnum_begin = messageA.find(b'stamp:') + 6
randnum_end = messageA.find(b'\x00')
randnum = bytes(str((int(messageA[randnum_begin:randnum_end]) + 1) % 256), encoding="utf8")
new_messageA = messageA[:randnum_begin] + randnum
new_messageA = adapt_message(new_messageA)
print("G:", new_messageA)
s = cipherC.encrypt(new_messageA)
s = hex(bytes_to_long(s))[2:]
io.sendlineafter("A: ", s)
io.recvuntil("B: ")
messageB = io.recvline().strip()
messageB = long_to_bytes(int(messageB, 16))
messageB = cipherC.decrypt(messageB.rjust((16 - len(messageB) % 16) % 16 + len(messageB), b'\x00'))
print("B:", messageB)
randnum_begin = messageB.find(b'stamp:') + 6
randnum_end = messageB.find(b'\x00')
randnum = bytes(str((int(messageB[randnum_begin:randnum_end]) + 1) % 256), encoding="utf8")
if i == 4:
new_messageB = b'I want to get my AES-key.\ntimestamp:' + randnum
else:
new_messageB = messageB[:randnum_begin] + randnum
new_messageB = adapt_message(new_messageB)
print("G:", new_messageB)
s = cipherS.encrypt(new_messageB)
s = hex(bytes_to_long(s))[2:]
io.sendlineafter("B: ", s)
io.recvuntil("A: ")
res = io.recvline().strip()
res = long_to_bytes(int(res, 16))
res = cipherS.decrypt(res.rjust((16 - len(res) % 16) % 16 + len(res), b'\x00'))
print(res)
enc_begin = res.find(b'key: ') + 5
enc_end = res.find(b'\ntimestamp')
enc = res[enc_begin:enc_end]
print('enc:', enc)
print("[++++++++++++++++] MITM conversation completed [++++++++++++++++]")
io.close()
return bytes_to_long(enc)


def partial_p(p0, kbits, n):
global state
global q
PR.<x> = PolynomialRing(Zmod(n))
f = 2^kbits*x + p0
f = f.monic()
roots = f.small_roots(X=2^(1400-kbits), beta=0.65)
if roots:
x0 = roots[0]
p = gcd(2^kbits*x0 + p0, n)
if ZZ(p):
q = int(p)
state = True


def find_p(d0, kbits, e, n):
global state
global q
X = var('X')
for k in range(1, e+1):
print("\r{}/{}".format(k, e), end="")
if state == True:
return
results = solve_mod([e*d0*X - k*X*(n-X+1) + k*n == X], 2^kbits)
for x in results:
p0 = ZZ(x[0])
if p0 > 1 and n % p0 == 0:
q = int(p0)
state = True
return
daemon_thread = threading.Thread(target=partial_p, args=(p0,kbits,n,), daemon=True)
daemon_thread.start()


def main():
encrypted_flag = get_encrypted_flag()
(e, n, d0) = gen_RSA_key()
(cipherC, cipherS) = MITM_connect()
c = MITM_conversation(cipherC, cipherS)
print("RSA_enc_key:", c)
d0 = Integer(d0)
kbits = d0.nbits()
find_p(d0, kbits, e, n)
p = n // q
d = inverse(e, (p-1)*(q-1))
print("[+] d = {}".format(d))
key = long_to_bytes(pow(c, d, n))
aes = AES.new(key, AES.MODE_ECB)
print(aes.decrypt(encrypted_flag))


if __name__ == "__main__":
main()

[More]

$size(e)=10$,也可能是虚拟机里跑sage不太可,脚本跑的时间看人品-.-