Skip to content

Commit dbb3cc2

Browse files
committed
added still broken box
1 parent 3869ae3 commit dbb3cc2

File tree

1 file changed

+155
-0
lines changed

1 file changed

+155
-0
lines changed

2016-09-16-csaw/still_broken_box/README.md

+155
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,160 @@
33
###ENG
44
[PL](#pl-version)
55

6+
The task was very similar to https://github.com/p4-team/ctf/tree/master/2016-09-16-csaw/broken_box but this time we could not recover all the bits of the key.
7+
We could get only some fraction of LSB bits, but there is a theorem stating that we need only n/4 of the LSB bits to recover full key, as long as `e` is reasonably small.
8+
9+
We used the same code as previously to mine faulty signatures and then to recover some bits of `d`.
10+
Then we applited mentioned theorem to recover the whole key.
11+
The recovery description can be found here: http://honors.cs.umd.edu/reports/lowexprsa.pdf
12+
13+
We used the sage code:
14+
15+
```sage
16+
# partial_d.sage
17+
18+
def partial_p(p0, kbits, n):
19+
PR.<x> = PolynomialRing(Zmod(n))
20+
nbits = n.nbits()
21+
22+
f = 2^kbits*x + p0
23+
f = f.monic()
24+
roots = f.small_roots(X=2^(nbits//2-kbits), beta=0.3) # find root < 2^(nbits//2-kbits) with factor >= n^0.3
25+
if roots:
26+
x0 = roots[0]
27+
p = gcd(2^kbits*x0 + p0, n)
28+
return ZZ(p)
29+
30+
def find_p(d0, kbits, e, n):
31+
X = var('X')
32+
33+
for k in xrange(1, e+1):
34+
results = solve_mod([e*d0*X - k*X*(n-X+1) + k*n == X], 2^kbits)
35+
for x in results:
36+
p0 = ZZ(x[0])
37+
p = partial_p(p0, kbits, n)
38+
if p:
39+
return p
40+
41+
42+
if __name__ == '__main__':
43+
print "start!"
44+
n = 123541066875660402939610015253549618669091153006444623444081648798612931426804474097249983622908131771026653322601466480170685973651622700515979315988600405563682920330486664845273165214922371767569956347920192959023447480720231820595590003596802409832935911909527048717061219934819426128006895966231433690709
45+
e = 97
46+
47+
beta = 0.5
48+
epsilon = beta^2/7
49+
50+
nbits = n.nbits()
51+
kbits = 300
52+
d0 = 48553333005218622988737502487331247543207235050962932759743329631099614121360173210513133
53+
print "lower %d bits (of %d bits) is given" % (kbits, nbits)
54+
55+
p = find_p(d0, kbits, e, n)
56+
print "found p: %d" % p
57+
```
58+
59+
Which gave us the modulus factor `p`.
60+
With that we simply decrypted the flag using:
61+
62+
```python
63+
import gmpy2
64+
65+
66+
def long_to_bytes(flag):
67+
flag = str(hex(flag))[2:-1]
68+
return "".join([chr(int(flag[i:i + 2], 16)) for i in range(0, len(flag), 2)])
69+
70+
p = 11508259255609528178782985672384489181881780969423759372962395789423779211087080016838545204916636221839732993706338791571211260830264085606598128514985547
71+
n = 123541066875660402939610015253549618669091153006444623444081648798612931426804474097249983622908131771026653322601466480170685973651622700515979315988600405563682920330486664845273165214922371767569956347920192959023447480720231820595590003596802409832935911909527048717061219934819426128006895966231433690709
72+
q = n/p
73+
e = 97
74+
75+
assert p*q == n
76+
d = gmpy2.invert(e, (p-1)*(q-1))
77+
flag = 96324328651790286788778856046571885085117129248440164819908629761899684992187199882096912386020351486347119102215930301618344267542238516817101594226031715106436981799725601978232124349967133056186019689358973953754021153934953745037828015077154740721029110650906574780619232691722849355713163780985059673037
78+
pt = pow(flag, d, n)
79+
print(long_to_bytes(pt))
80+
```
81+
82+
and got `flag{n3v3r_l34k_4ny_51n6l3_b17_0f_pr1v473_k3y}`
683

784
###PL version
85+
86+
Zadanie było bardzo podobne do https://github.com/p4-team/ctf/tree/master/2016-09-16-csaw/broken_box ale tym razem nie mogliśmy odzyskać wszystkich bitów klucza.
87+
Mogliśmy dostać jedynie część niskich bitów klucza, niemniej istnieje twierdzenie mówiące że wystarczy nam n/4 najniższych bitów do odzyskania całego klucza, o ile `e` jest względnie małe.
88+
89+
Użyliśmy tego samego kodu co wcześniej aby pobrać błędne sygnatury i odzyskać niskie bity `d`.
90+
Następnie zastosowaliśmy wspomniane twierdzenie aby odzyskać cały klucz.
91+
Opis mechanizmu odzyskiwania klucza można znaleźć tu: http://honors.cs.umd.edu/reports/lowexprsa.pdf
92+
93+
Użyliśmy kodu:
94+
95+
```sage
96+
# partial_d.sage
97+
98+
def partial_p(p0, kbits, n):
99+
PR.<x> = PolynomialRing(Zmod(n))
100+
nbits = n.nbits()
101+
102+
f = 2^kbits*x + p0
103+
f = f.monic()
104+
roots = f.small_roots(X=2^(nbits//2-kbits), beta=0.3) # find root < 2^(nbits//2-kbits) with factor >= n^0.3
105+
if roots:
106+
x0 = roots[0]
107+
p = gcd(2^kbits*x0 + p0, n)
108+
return ZZ(p)
109+
110+
def find_p(d0, kbits, e, n):
111+
X = var('X')
112+
113+
for k in xrange(1, e+1):
114+
results = solve_mod([e*d0*X - k*X*(n-X+1) + k*n == X], 2^kbits)
115+
for x in results:
116+
p0 = ZZ(x[0])
117+
p = partial_p(p0, kbits, n)
118+
if p:
119+
return p
120+
121+
122+
if __name__ == '__main__':
123+
print "start!"
124+
n = 123541066875660402939610015253549618669091153006444623444081648798612931426804474097249983622908131771026653322601466480170685973651622700515979315988600405563682920330486664845273165214922371767569956347920192959023447480720231820595590003596802409832935911909527048717061219934819426128006895966231433690709
125+
e = 97
126+
127+
beta = 0.5
128+
epsilon = beta^2/7
129+
130+
nbits = n.nbits()
131+
kbits = 300
132+
d0 = 48553333005218622988737502487331247543207235050962932759743329631099614121360173210513133
133+
print "lower %d bits (of %d bits) is given" % (kbits, nbits)
134+
135+
p = find_p(d0, kbits, e, n)
136+
print "found p: %d" % p
137+
```
138+
139+
Który dał nam czynnik `p` modulusa.
140+
Następnie po prostu zdeszyfrowaliśmy flagę:
141+
142+
```python
143+
import gmpy2
144+
145+
146+
def long_to_bytes(flag):
147+
flag = str(hex(flag))[2:-1]
148+
return "".join([chr(int(flag[i:i + 2], 16)) for i in range(0, len(flag), 2)])
149+
150+
p = 11508259255609528178782985672384489181881780969423759372962395789423779211087080016838545204916636221839732993706338791571211260830264085606598128514985547
151+
n = 123541066875660402939610015253549618669091153006444623444081648798612931426804474097249983622908131771026653322601466480170685973651622700515979315988600405563682920330486664845273165214922371767569956347920192959023447480720231820595590003596802409832935911909527048717061219934819426128006895966231433690709
152+
q = n/p
153+
e = 97
154+
155+
assert p*q == n
156+
d = gmpy2.invert(e, (p-1)*(q-1))
157+
flag = 96324328651790286788778856046571885085117129248440164819908629761899684992187199882096912386020351486347119102215930301618344267542238516817101594226031715106436981799725601978232124349967133056186019689358973953754021153934953745037828015077154740721029110650906574780619232691722849355713163780985059673037
158+
pt = pow(flag, d, n)
159+
print(long_to_bytes(pt))
160+
```
161+
162+
i dostalismy `flag{n3v3r_l34k_4ny_51n6l3_b17_0f_pr1v473_k3y}

0 commit comments

Comments
 (0)