# corCTF 2022
- [ ] Có giải thích (Added explanation)
- [x] [tadpole](#tadpole---262-solves)
- [x] [luckyguess](#luckyguess---150-solves)
- [x] [exchange](#exchange---94-solves)
- [x] [hidE](#hidE---88-solves)
- [ ] [generous](#generous---49-solves)
- [ ] [corrupted-curves](#corrupted-curves---20-solves)
- [ ] [threetreasures](#threetreasures---19-solves)
## tadpole - 262 solves
> tadpoles only know the alphabet up to b... how will they ever know what p is?
Attachments:
1. tadpole.py
```python=0
from Crypto.Util.number import bytes_to_long, isPrime
from secrets import randbelow
p = bytes_to_long(open("flag.txt", "rb").read())
assert isPrime(p)
a = randbelow(p)
b = randbelow(p)
def f(s):
return (a * s + b) % p
print("a = ", a)
print("b = ", b)
print("f(31337) = ", f(31337))
print("f(f(31337)) = ", f(f(31337)))
```
* output.txt
```
a = 7904681699700731398014734140051852539595806699214201704996640156917030632322659247608208994194840235514587046537148300460058962186080655943804500265088604049870276334033409850015651340974377752209566343260236095126079946537115705967909011471361527517536608234561184232228641232031445095605905800675590040729
b = 16276123569406561065481657801212560821090379741833362117064628294630146690975007397274564762071994252430611109538448562330994891595998956302505598671868738461167036849263008183930906881997588494441620076078667417828837239330797541019054284027314592321358909551790371565447129285494856611848340083448507929914
f(31337) = 52926479498929750044944450970022719277159248911867759992013481774911823190312079157541825423250020665153531167070545276398175787563829542933394906173782217836783565154742242903537987641141610732290449825336292689379131350316072955262065808081711030055841841406454441280215520187695501682433223390854051207100
f(f(31337)) = 65547980822717919074991147621216627925232640728803041128894527143789172030203362875900831296779973655308791371486165705460914922484808659375299900737148358509883361622225046840011907835671004704947767016613458301891561318029714351016012481309583866288472491239769813776978841785764693181622804797533665463949
```
Phân tích file `tadpole.py` ta thấy có hàm `f()` với công thức: $f\left(x\right) = ax + b$
File `output.txt` cho ta biết $a$, $b$, $f(31337)\bmod{p}$ (mình sẽ gọi $f_1$), $f(f(31337))\bmod{p}$ (cái này thì $f_2$). Flag chính là số chia $p$ dùng để chia dư sau khi tính $f(x)$. Cách giải:
* Ta có:
\begin{align}
f(31337) &= 31337a + b \\
&= k_1p + f_1 \\
\iff f(31337) - f_1 &= k_1p \\
\end{align}
* Ta lại có:
\begin{align}
f(f(31337)) &= f_1a + b \\
&= k_2p + f_2 \\
\iff f(f(31337)) - f_2 &= k_2p \\
\end{align}
* Kết luận:
\begin{align}
\mathit{gcd}\left(f(31337)-f_1, f(f(31337))-f_2\right) &= \mathit{gcd}(k_1, k_2)p \\
\iff p &= \frac{\mathit{gcd}\left(f(31337)-f_1, f(f(31337))-f_2\right)}{\mathit{gcd}(k_1, k_2)}
\end{align}
Vì bài này $f_1$ có $x = 31337$ do đó $k_1$ còn bé hơn 31337. Nên nếu $\mathit{gcd}(k_1, k_2)$ không bằng 1 thì ta cũng có thể bruteforce tìm $p$ được.
Script bằng **python**:
```python=0
from gmpy2 import gcd, is_prime
from Crypto.Util.number import long_to_bytes
a = 7904681699700731398014734140051852539595806699214201704996640156917030632322659247608208994194840235514587046537148300460058962186080655943804500265088604049870276334033409850015651340974377752209566343260236095126079946537115705967909011471361527517536608234561184232228641232031445095605905800675590040729
b = 16276123569406561065481657801212560821090379741833362117064628294630146690975007397274564762071994252430611109538448562330994891595998956302505598671868738461167036849263008183930906881997588494441620076078667417828837239330797541019054284027314592321358909551790371565447129285494856611848340083448507929914
f1= 52926479498929750044944450970022719277159248911867759992013481774911823190312079157541825423250020665153531167070545276398175787563829542933394906173782217836783565154742242903537987641141610732290449825336292689379131350316072955262065808081711030055841841406454441280215520187695501682433223390854051207100
f2= 65547980822717919074991147621216627925232640728803041128894527143789172030203362875900831296779973655308791371486165705460914922484808659375299900737148358509883361622225046840011907835671004704947767016613458301891561318029714351016012481309583866288472491239769813776978841785764693181622804797533665463949
def f(s):
return a * s + b
p = gcd(f(f1)-f2, f(31337)-f1)
k = 2
while not is_prime(p):
if p % k == 0:
p //= k
k += 1
print(long_to_bytes(p).decode())
```
## luckyguess - 150 solves
> i hope you're feeling lucky today
> ==*nc be.ax 31800*==
Atachment:
1. luckyguess.py
```python=
#!/usr/local/bin/python
from random import getrandbits
p = 2**521 - 1
a = getrandbits(521)
b = getrandbits(521)
print("a =", a)
print("b =", b)
try:
x = int(input("enter your starting point: "))
y = int(input("alright, what's your guess? "))
except:
print("?")
exit(-1)
r = getrandbits(20)
for _ in range(r):
x = (x * a + b) % p
if x == y:
print("wow, you are truly psychic! here, have a flag:", open("flag.txt").read())
else:
print("sorry, you are not a true psychic... better luck next time")
```
Bài này giống với bài trước ở chổ dùng hàm `f()`. Vì bài trước không cần đụng quá sâu vào phân tích hàm này nhưng bài này và bài sau sẽ cần đi sâu hơn vào hàm `f()` hay còn gọi là **Linear Congruential Generator**(không biết dịch :crying_cat_face: ), gọi là LCG luôn nha.
Trong LCG thì ta sẽ có 3 tham số là số nhân $a$, số tăng $b$ và số chia $p$. Công thức để sinh 1 số tiếp theo:
$$
X_{n} = aX_{n-1} + b\pmod{p}
$$
Hay công thức tổng quát sinh 1 số sau $n$ lần sinh:
\begin{align}
X_{n} &= a^nX + b\left(a^{n-1} + a^{n-2} + \dots + 1\right)\pmod{p} \\
&= a^nX + b\frac{a^n-1}{a-1}\pmod{p} \\
\end{align}
Phân tích bài thì mình thấy là muốn là lấy được Flag, ta cần nhập vào 2 số $x$ và $y$ sao cho $y$ là kết quả của $x$ sau 1 số ngẫu nhiên $n$ lần sinh từ LCG (với $a, b, p$ đã cho trước) thì sẽ hiện Flag. $n$ được tạo ngẫu nhiên sau khi nhập $x$ và $y$ do đó chứng tỏ luôn tồn tại số mà số lần sinh bao nhiêu đi nữa thì vẫn chỉ có một đáp án được sinh ra duy nhất. Chứng minh:
$$
\begin{align}
X_n &= a^nX + b\frac{a^n-1}{a-1}\pmod{p} \\
&= a^nX + \frac{a^nb}{a-1} - \frac{b}{a-1} \\
&= a^n\left(X + \frac{b}{a-1}\right)- \frac{b}{a-1} \pmod{p}
\end{align}
\\
\implies\text{ Với }X = \frac{-b}{a-1}\pmod{p}\text{ thì } X_n = \frac{-b}{a-1}\pmod{p}\ \forall n \in \mathbb{Z}
$$
Như vậy ta chỉ cần gửi 2 số $x = y = \frac{-b}{a-1}$ là thoả đề bài bất chấp $n$
Script bằng python:
```python=
from telnetlib import Telnet
from gmpy2 import powmod, mpz, invert
HOST='be.ax'
io=Telnet(HOST, 31800)
def readline(io):
return io.read_until(b'\n').strip()
def writeline(io,a):
io.write(a+b'\n')
def writelineafter(io,a,b):
io.read_until(a)
io.write(b+b'\n')
p = 2**521 - 1
a = mpz(readline(io).decode().split(' = ')[-1])
b = mpz(readline(io).decode().split(' = ')[-1])
print(f'{a = }')
print(f'{b = }')
d=b*invert(a-1, p) % p
def mult(s, n):
return (powmod(a, n, p)*(s+d) - d) % p
x=-d % p
print(f'{x = }')
writelineafter(io, b'enter your starting point: ', str(x).encode())
writelineafter(io, b"alright, what's your guess? ", str(x).encode())
io.interact()
```
```
a = mpz(5815852120647221830743456624914356390039371105830699270464029590874475456657117550074855812011884480607802918282419847545467433600776148519838488752631362952)
b = mpz(952181975324355351570551489379321109065547547255327479943224484487947395583129880350817984292577909751440515965493779280220863275205203433947008756513743810)
x = mpz(4166248842373150773632623416381435197263539095549119825550632267999141448474641407273907289361842204021767551168821838666596572981635445121985783739875667990)
wow, you are truly psychic! here, have a flag: corctf{r34l_psych1c5_d0nt_n33d_f1x3d_p01nt5_t0_tr1ck_th15_lcg!}
```
## exchange - 94 solves
> you could make an exchange out of this
Attachments:
1. exchange.py
```python=
from Crypto.Util.number import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from hashlib import sha256
from secrets import randbelow
p = 142031099029600410074857132245225995042133907174773113428619183542435280521982827908693709967174895346639746117298434598064909317599742674575275028013832939859778024440938714958561951083471842387497181706195805000375824824688304388119038321175358608957437054475286727321806430701729130544065757189542110211847
a = randbelow(p)
b = randbelow(p)
s = randbelow(p)
print("p =", p)
print("a =", a)
print("b =", b)
print("s =", s)
a_priv = randbelow(p)
b_priv = randbelow(p)
def f(s):
return (a * s + b) % p
def mult(s, n):
for _ in range(n):
s = f(s)
return s
A = mult(s, a_priv)
B = mult(s, b_priv)
print("A =", A)
print("B =", B)
shared = mult(A, b_priv)
assert mult(B, a_priv) == shared
flag = open("flag.txt", "rb").read()
key = sha256(long_to_bytes(shared)).digest()[:16]
iv = long_to_bytes(randint(0, 2**128))
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
print(iv.hex() + cipher.encrypt(pad(flag, 16)).hex())
```
2. output.txt
```
p = 142031099029600410074857132245225995042133907174773113428619183542435280521982827908693709967174895346639746117298434598064909317599742674575275028013832939859778024440938714958561951083471842387497181706195805000375824824688304388119038321175358608957437054475286727321806430701729130544065757189542110211847
a = 118090659823726532118457015460393501353551257181901234830868805299366725758012165845638977878322282762929021570278435511082796994178870962500440332899721398426189888618654464380851733007647761349698218193871563040337609238025971961729401986114391957513108804134147523112841191971447906617102015540889276702905
b = 57950149871006152434673020146375196555892205626959676251724410016184935825712508121123309360222777559827093965468965268147720027647842492655071706063669328135127202250040935414836416360350924218462798003878266563205893267635176851677889275076622582116735064397099811275094311855310291134721254402338711815917
s = 35701581351111604654913348867007078339402691770410368133625030427202791057766853103510974089592411344065769957370802617378495161837442670157827768677411871042401500071366317439681461271483880858007469502453361706001973441902698612564888892738986839322028935932565866492285930239231621460094395437739108335763
A = 27055699502555282613679205402426727304359886337822675232856463708560598772666004663660052528328692282077165590259495090388216629240053397041429587052611133163886938471164829537589711598253115270161090086180001501227164925199272064309777701514693535680247097233110602308486009083412543129797852747444605837628
B = 132178320037112737009726468367471898242195923568158234871773607005424001152694338993978703689030147215843125095282272730052868843423659165019475476788785426513627877574198334376818205173785102362137159225281640301442638067549414775820844039938433118586793458501467811405967773962568614238426424346683176754273
e0364f9f55fc27fc46f3ab1dc9db48fa482eae28750eaba12f4f76091b099b01fdb64212f66caa6f366934c3b9929bad37997b3f9d071ce3c74d3e36acb26d6efc9caa2508ed023828583a236400d64e
```
Câu này là 1 dạng trao đổi khoá Diffie-Hellman nhưng dựa trên LCG: $s_{n} = as_{n-1} + b\pmod{p}$. Ta được cho biết $a, b, p, s, A, B$ với $A$ và $B$ lần lượt là khoá công khai của Alice và Bob, $A=s_{n_a}$ và $B=s_{n_B}$. Yêu cầu là cần tìm $n_A$ hoặc $n_B$ từ đó tính $s_{n_A+n_B}$ rồi băm ra lấy được `key` và giải mã `Flag`.
Đây là một bài toán ==discrete log== điển hình nhưng trên LCG. Áp dụng công thức đã chứng minh ở câu trên, ta có:
\begin{align}
s_{n_A} &= a^{n_A}(s+\frac{b}{a-1}) - \frac{b}{a-1} \pmod{p} \\
s_{n_A} + \frac{b}{a-1} &= a^{n_A}(s+\frac{b}{a-1}) \pmod{p} \\
\frac{s_{n_A} + \frac{b}{a-1}}{s+\frac{b}{a-1}} &= a^{n_A} \pmod{p} \\
\end{align}
Tất cả các số ta đều có trừ $n_A$, do đó ta đã thành công đưa về bài toán ==discrete log== trong $(\mathbb{Z}/\mathcal{p}\mathbb{Z})^{\mathbb{x}}$. Cộng với việc $p-1$ **smooth**, ta có thể sử dụng thuật toán **Pollig-Hellman** để tìm $n_A$ dễ dàng từ $a^{n_A}$.
Sau đó tìm `shared` rồi băm ra thành khoá giải mã.
Thuật toán Pollig-Hellman: [wiki](https://en.m.wikipedia.org/wiki/Pohlig%E2%80%93Hellman_algorithm)
Hàm `discrele_log` trong sagemath sử dụng ==Pollig-Hellman== kết hợp ==Baby-step/Giant-step== để giải các bài toán logarithm rời rạc. Vì `muliplicative_order` của $a$ trên $p$ bằng $p-1$ là 1 số smooth(có các ước nguyên tố nhỏ), do đó ==Pollig-Hellman== đã giảm độ phức tạp tìm kiếm xuống ngang với các số nguyên tố bé đó giúp ==Baby-step/Giant-step== tìm số mũ nhỏ 1 cách nhanh chóng. Sau khi tìm được từng số mũ ứng với các số nguyên tố nhỏ ($(\mathbb{Z}/\mathcal{p_i}\mathbb{Z})^{\mathbb{x}}$ với $p_i$ là các ước nguyên tố của $p$), sử dụng [Định lý số dư Trung Hoa](https://en.m.wikipedia.org/wiki/Chinese_remainder_theorem) để tìm số mũ lớn cuối cùng trên $(\mathbb{Z}/\mathcal{p}\mathbb{Z})^{\mathbb{x}}$. Script bằng sagemath:
```python=
from Crypto.Util.number import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from hashlib import sha256
from sage.all import *
p = 142031099029600410074857132245225995042133907174773113428619183542435280521982827908693709967174895346639746117298434598064909317599742674575275028013832939859778024440938714958561951083471842387497181706195805000375824824688304388119038321175358608957437054475286727321806430701729130544065757189542110211847
a = 118090659823726532118457015460393501353551257181901234830868805299366725758012165845638977878322282762929021570278435511082796994178870962500440332899721398426189888618654464380851733007647761349698218193871563040337609238025971961729401986114391957513108804134147523112841191971447906617102015540889276702905
b = 57950149871006152434673020146375196555892205626959676251724410016184935825712508121123309360222777559827093965468965268147720027647842492655071706063669328135127202250040935414836416360350924218462798003878266563205893267635176851677889275076622582116735064397099811275094311855310291134721254402338711815917
s = 35701581351111604654913348867007078339402691770410368133625030427202791057766853103510974089592411344065769957370802617378495161837442670157827768677411871042401500071366317439681461271483880858007469502453361706001973441902698612564888892738986839322028935932565866492285930239231621460094395437739108335763
A = 27055699502555282613679205402426727304359886337822675232856463708560598772666004663660052528328692282077165590259495090388216629240053397041429587052611133163886938471164829537589711598253115270161090086180001501227164925199272064309777701514693535680247097233110602308486009083412543129797852747444605837628
B = 132178320037112737009726468367471898242195923568158234871773607005424001152694338993978703689030147215843125095282272730052868843423659165019475476788785426513627877574198334376818205173785102362137159225281640301442638067549414775820844039938433118586793458501467811405967773962568614238426424346683176754273
enc='e0364f9f55fc27fc46f3ab1dc9db48fa482eae28750eaba12f4f76091b099b01fdb64212f66caa6f366934c3b9929bad37997b3f9d071ce3c74d3e36acb26d6efc9caa2508ed023828583a236400d64e'
def f(s):
return (a * s + b) % p
F=GF(p)
a=F(a)
b=F(b)
s=F(s)
A=F(A)
B=F(B)
d=b/(a-1)
def mult(s, n):
return (a^n)*(s+d) - d
def logp(h, g):
x=h
x += d
x /= (g+d)
return discrete_log(x, a)
a_priv=logp(A, s)
print(a_priv)
shared=mult(B, a_priv)
key = sha256(long_to_bytes(shared)).digest()[:16]
iv = bytes.fromhex(enc[:32])
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
print(unpad(cipher.decrypt(bytes.fromhex(enc[32:])), 16).decode())
```
```
63497966771228335993935218724355716676359926182967975463093060105894867914802051403524263838451533117998140812842659902100945139428076742018151358770711075499718955791059569760306592803008376586431708302909649602374612770031446609941268056564386982932718212036534269872682126736591975854896102855270700008372
corctf{th1s_lcg_3xch4ng3_1s_4_l1ttl3_1ns3cur3_f0r_n0w}
```
## hidE - 88 solves
> This RSA encryption service is so secure we're not even going to tell you how we encrypted it
> ==nc be.ax 31124==
Attachments:
1. main.py
```python=
#!/usr/local/bin/python
import random
import time
import math
import binascii
from Crypto.Util.number import *
p, q = getPrime(512), getPrime(512)
n = p * q
phi = (p - 1) * (q - 1)
flag = open('./flag.txt').read().encode()
random.seed(int(time.time()))
def encrypt(msg):
e = random.randint(1, n)
while math.gcd(e, phi) != 1:
e = random.randint(1, n)
pt = bytes_to_long(msg)
ct = pow(pt, e, n)
return binascii.hexlify(long_to_bytes(ct)).decode()
def main():
print('Secure Encryption Service')
print('Your modulus is:', n)
while True:
print('Options')
print('-------')
print('(1) Encrypt flag')
print('(2) Encrypt message')
print('(3) Quit')
x = input('Choose an option: ')
if x not in '123':
print('Unrecognized option.')
exit()
elif x == '1':
print('Here is your encrypted flag:', encrypt(flag))
elif x == '2':
msg = input('Enter your message in hex: ')
print('Here is your encrypted message:', encrypt(binascii.unhexlify(msg)))
elif x == '3':
print('Bye')
exit()
if __name__ == '__main__':
main()
```
Câu này là một lỗi cơ bản của RSA, dùng chung số modulus $n$ cho nhiều lần mã hoá nhưng mỗi lần mã hoá lain dùng 1 $e$ khác nhau.
Giải thuật Euclid mở rộng chỉ ra rằng nếu $d = \mathit{GCD}(a, b)$ thì luôn tồn tại 2 giá trị nguyên $x, y$ sao cho:
$$ax + by = d$$
Từ công thức trên, nếu ta lấy được từ server 2 **Flag** đã bị mã hoá bằng 2 $e$ khác nhau và $\mathit{GCD}(e_1, e_2)=1$. Lúc này sử dụng giải thuật Euclid mở rộng tìm ra 2 số $x, y$ thoả:
$$e_1x+e_2y=1$$
Cụ thể:
\begin{align}
c_1^xc_2^y &\equiv (m^{e_1})^x(m^{e_2})^y \pmod{n} \\
&\equiv m^{e_1x+e_2y} \pmod{n} \\
&\equiv m \pmod{n}
\end{align}
Mặc dù server không cho ta thấy $e$ nhưng lại dùng `seed` là `time.time()` (thời gian hiện tại), nên ta hoàn toàn biết trước được server đã `random` ra số nào. Server chỉ chọn những $e$ có $\mathit{gcd}(e, phi)=1$ mà ta lại không biết $phi$ nên mình sẽ tạo 1 lần 20 số $e$ (lấy số lẻ thôi vì $phi$ luôn chẵn) và lấy ra tất cả tổ hợp 2 số trong 20 số $e$ đó để dùng giải thuật euclid tìm **Flag** thử.
Lặp đi lặp lại đến khi có **Flag** thì thôi. Script bằng python:
```python=
import random
import time
from telnetlib import Telnet
from gmpy2 import mpz, powmod
from Crypto.Util.number import long_to_bytes
from itertools import combinations
def readline():
return io.read_until(b'\n').strip()
def writeline(a):
io.write(a+b'\n')
def writelineafter(a,b):
io.read_until(a)
io.write(b+b'\n')
def sendoption(a):
writelineafter(b'Choose an option: ',str(a).encode())
def getflag():
sendoption(1)
res=readline().decode()
return res.split(': ')[-1]
def egcd(a, b):
if a == 0:
return (b, 0, 1)
else:
g, x, y = egcd(b % a, a)
return (g, y - (b // a) * x, x)
while True:
HOST='be.ax'
io=Telnet(HOST, 31124)
t=int(time.time())
random.seed(t)
print('*'*20, 'time =', t, '*'*20)
readline()
n=mpz(readline().decode().split(': ')[-1])
print('n =', n)
enc1=mpz(getflag(), 16)
print('enc_flag =', enc1)
enc2=mpz(getflag(), 16)
print('enc_flag =', enc2)
es=[]
for i in range(20):
e = random.randint(1, n)
while e%2 == 0:
e = random.randint(1, n)
es.append(e)
for ee in combinations(es, 2):
e1, e2 = ee
g, x, y = egcd(e1, e2)
if g>1:
continue
m1 = powmod(enc1, x, n)
m2 = powmod(enc2, y, n)
m = (m1 * m2) % n
flag=long_to_bytes(m)
if b'corctf' in flag:
print('Flag:', flag.decode())
exit()
```
Một phát được luôn này =))
```
******************** time = 1660533167 ********************
n = 71801129361761221506154609745995886583695621533063171597520933770446332171041575649152508516201009762312913762704234742518226822856803457217363549518685648184230977945707284788062854185363735316578544380517526584155929138979880549293956304995032341892137257316530769335248274546713892934689862219179090342387
enc_flag = 3377628949218061168453738285772637160938243516024918800012735905348622180723225709358555530381858456968564182689080064903503516500262077496365892425220971263663148355670499492751291535504662382295408850204238012866940543243483683686475874684631219973597987694056995625713543222717401520546305306251658018238
enc_flag = 47552380856364571942923570580889278085982214976056322300507315594593302628985583058925934079483474137761213474543871901386636641283495080306878956064909935647160002991159435705450734756996210806608388936970814971237577948358453047403062454783190327513173358068325218195860137972390205404877823865433599382022
Flag: corctf{y34h_th4t_w4snt_v3ry_h1dd3n_tbh_l0l}
```
## generous - 49 solves
Attachments:
1. generous.py
```python=
#!/usr/local/bin/python
from Crypto.Util.number import getPrime, inverse, bytes_to_long
from random import randrange
with open("flag.txt", "rb") as f:
flag = f.read().strip()
def gen_keypair():
p, q = getPrime(512), getPrime(512)
n = (p**2) * q
while True:
g = randrange(2, n)
if pow(g, p-1, p**2) != 1:
break
h = pow(g, n, n)
return (n, g, h), (g, p, q)
def encrypt(pubkey, m):
n, g, h = pubkey
r = randrange(1, n)
c = pow(g, m, n) * pow(h, r, n) % n
return c
def decrypt(privkey, c):
g, p, q = privkey
a = (pow(c, p-1, p**2) - 1) // p
b = (pow(g, p-1, p**2) - 1) // p
m = a * inverse(b, p) % p
return m
def oracle(privkey, c):
m = decrypt(privkey, c)
return m % 2
pub, priv = gen_keypair()
n, g, h = pub
print(f"Public Key:\n{n = }\n{g = }\n{h = }")
print(f"Encrypted Flag: {encrypt(pub, bytes_to_long(flag))}")
while True:
inp = int(input("Enter ciphertext> "))
print(f"Oracle result: {oracle(priv, inp)}")
```
Bài này cho phép giải mã `input` chúng ta gửi lên nhưng chỉ leak 1 bit cuối sau khi giải mã.
Script bằng python:
```python=
from telnetlib import Telnet
from gmpy2 import powmod, mpz, invert
from random import randrange
from Crypto.Util.number import long_to_bytes
def readline():
return io.read_until(b'\n').strip()
def writeline(a):
io.write(a+b'\n')
def writelineafter(a,b):
io.read_until(a)
io.write(b+b'\n')
def encrypt(pubkey, m):
n, g, h = pubkey
r = randrange(1, n)
c = powmod(g, m, n) * powmod(h, r, n) % n
return c
def oracle(enc):
writelineafter(b"Enter ciphertext> ", str(enc).encode())
res=readline().decode()
print(res)
return res[-1]
HOST='be.ax'
io=Telnet(HOST, 31244)
print(readline().decode())
n=mpz(readline().decode().split(' = ')[-1])
g=mpz(readline().decode().split(' = ')[-1])
h=mpz(readline().decode().split(' = ')[-1])
print(f'{n = }\n{g = }\n{h = }')
enc=mpz(readline().decode().split(': ')[-1])
print(f'{enc = }')
flag=''
while True:
bit = oracle(enc)
flag=bit + flag
if bit == '1':
enc=(enc*encrypt((n,g,h), -1 % n))%n
enc = pow(enc, invert(2, n), n)
msg=long_to_bytes(int(flag, 2))
if b'corctf' in msg:
print(msg.decode())
break
```
```
Public Key:
n = mpz(646216861115612033449639030716237139242303555060604399178522250048092454138457122953862614101074260300396163373370955458865545073300064652603555099441639621756297195641894434730473072428980293466066451967172976918994480827032576657933404175976794466635850361956060543485278258089021033433810981861958861935967832240177397324429437564496950671547952321478343929164684394853121179892434275315451742695675655668241008818346609867613694593006602324068982847663683891)
g = mpz(307246213620426918687914319918159880944972247416473590525876006288125706142679224216321356495825787357048475830956967557360393024510462960460124326154878966008584802605557741951939387452642456572262466259739191319302236302042812359123996959272362778228151815421795770442777505193052762458624237488247511342605983657206962251805731014508144193052112122014889173854833202658547932585771378422294110407648863329904642400946601751115360819653065955862283687814994257)
h = mpz(609970360087891514690232313269440347100333736247341804674307577861167452920911277278031444704051063146570044200879868563131802967842122116419285941302445712354401728232111614438434354357990946515005799437535851345930919490597134688600466558843985891854033492932170044464994996924768734055608936635835441090764590896358056975689462953352979048143948974489967610366449653874440181775660391125045198990167774816614329350765655171997528896171081201452182633035438535)
enc = mpz(570122027895082305574434470604509118820984251810326905638073001986284293858498056381970752863841200558716606640464054346661840054472669237409699110115636900381998413505100376774553232009134178194752521909102074882921998798261232075728735456107654387893651405806410688142641410405826983811613037468562218154538982918764808599253374298386323910140782182337740318510275833141281915070927876183029255523755974814340874461902981432033108929922442516077661754210549548)
Oracle result: 1
Oracle result: 0
Oracle result: 1
Oracle result: 1
Oracle result: 1
Oracle result: 1
...
Oracle result: 1
Oracle result: 0
Oracle result: 0
Oracle result: 0
Oracle result: 1
Oracle result: 1
corctf{see?1_bit_is_very_generous_of_me}
```
## corrupted-curves - 20 solves
> all these fake curves, but where's the real one?
> ==nc be.ax 31345==
Attachments:
1. corruptedcurves.py
```python=
#!/usr/local/bin/python
from secrets import randbits
from Crypto.Util.number import getPrime
def square_root(a, p):
if legendre_symbol(a, p) != 1:
return 0
elif a == 0:
return 0
elif p == 2:
return 0
elif p % 4 == 3:
return pow(a, (p + 1) // 4, p)
s = p - 1
e = 0
while s % 2 == 0:
s //= 2
e += 1
n = 2
while legendre_symbol(n, p) != -1:
n += 1
x = pow(a, (s + 1) // 2, p)
b = pow(a, s, p)
g = pow(n, s, p)
r = e
while True:
t = b
m = 0
for m in range(r):
if t == 1:
break
t = pow(t, 2, p)
if m == 0:
return x
gs = pow(g, 2 ** (r - m - 1), p)
g = (gs * gs) % p
x = (x * gs) % p
b = (b * g) % p
r = m
def legendre_symbol(a, p):
ls = pow(a, (p - 1) // 2, p)
return -1 if ls == p - 1 else ls
class EllipticCurve:
def __init__(self, p, a, b):
self.a = a
self.b = b
self.p = p
if not self.check_curve():
raise Exception("Not an elliptic curve!")
def check_curve(self):
discrim = -16 * (4*pow(self.a, 3) + 27*pow(self.b, 2))
if discrim % self.p:
return 1
return 0
def lift_x(self, px):
y2 = (pow(px, 3) + self.a*px + self.b) % self.p
py = square_root(y2, self.p)
if py == 0:
raise Exception("No point on elliptic curve.")
return py
with open("flag.txt", "rb") as f:
flag = f.read()
flag = int.from_bytes(flag, 'big')
print("Generating parameters...")
while True:
p = getPrime(512)
a, b = randbits(384), randbits(384)
try:
E = EllipticCurve(p, a, b)
fy = E.lift_x(flag)
print(f"p = {p}")
print(f"flag y = {fy}")
break
except:
continue
checked = set()
count = 0
while count < 64:
x = int(input("x = ")) % p
if int(x) in checked or x < 2**384 or abs(x - p) < 2**384:
print(">:(")
continue
e = randbits(64)
print(f"e = {e}")
try:
E = EllipticCurve(p, a^e, b^e)
py = E.lift_x(x)
checked.add(int(x))
print(f"y = {py}")
count += 1
except:
print(":(")
print("bye!")
```
Script bằng sagemath:
```python=
from telnetlib import Telnet
from gmpy2 import mpz
from secrets import randbits
need=70
icon1=b">:("
icon2=b":("
def readline():
return io.read_until(b'\n').strip()
def writeline(a):
io.write(a+b'\n')
def writelineafter(a,b):
io.read_until(a)
io.write(b+b'\n')
def solve(res, e):
l1, l2 = res
x=l1[-1]
t1=(l1[0] - pow(l1[-1], 3))%p
t2=(l2[0] - pow(l2[-1], 3))%p
_a=(t2 - t1)%p
if _a.bit_length() > 450: return None
_a = _a % x
_a = (_a >> need) << need
_b = ((t1 - _a*x) % p) % x
_b = (_b >> need) << need
print(f"high_a = {_a}")
print(f"high_b = {_b}")
t3 = (t1 - _a*x - _b) % p
a_=t3//x
b_=t3%x
print(f"low_a = {a_^^e}")
print(f"low_b = {b_^^e}")
a=(_a + a_)^^e
b=(_b + b_)^^e
return (a, b)
HOST='be.ax'
io=Telnet(HOST, 31345)
readline().decode()
p=mpz(readline().decode().split(' = ')[-1])
flagy=mpz(readline().decode().split(' = ')[-1])
print(f"p = {p}")
print(f"flagy = {flagy}")
count=0
while count < 64:
_x=randbits(385)
xs=[_x, _x+1]
res=[]
es=[]
for i in range(2):
x=xs[i]
writelineafter(b"x = ", str(x).encode())
line=readline()
if line == icon1:
break
es.append(mpz(line.decode().split(' = ')[-1]))
line=readline()
if line == icon2:
break
y=mpz(line.decode().split(' = ')[-1])
res.append([(y**2)%p, x])
count += 1
if len(res) != 2: continue
ans=solve(res, es[0])
if not ans:
continue
a, b= ans
if res[0][0] == (pow(_x, 3) + (a^^es[0])*_x +(b^^es[0]))%p:
print(f"a = {a}")
print(f"b = {b}")
break
else:
print('Not Found!')
y2=GF(p)(flagy^2)
PF.<x>=PolynomialRing(GF(p))
f=x^3 + a*x + b - y2
x=int(f.roots()[0][0])
print('x =', x)
print('Flag:', x.to_bytes((x.bit_length()+7)//8, 'big').decode())
```
> `^^` là phép xOR trong sagemath vì `^` đã được định nghĩa là lũy thừa
```
p = 13387807327366432118019435649047642368794794612950022741671500540028456588862522492073493363345871340671655824429479684632787052830134235256218924489281787
flagy = 111015323710222217749171570085093618604205755655636018359214210282667133020770881013460387419077373610240473179231724512266173323071887945194208209741945
high_a = 37561463133651386926453198044340289225989571282932296022150868643063209614243862186848561552288136193975296160956416
high_b = 2297366883974705026856556566267462468158092716966249445677867944340874163700063885162721333224672415808786813419520
low_a = 156858732709096936120
low_b = 455319443649105001759
a = 37561463133651386926453198044340289225989571282932296022150868643063209614243862186848561552288293052708005257892536
b = 2297366883974705026856556566267462468158092716966249445677867944340874163700063885162721333225127735252435918421279
x = 5207851285831991063142066581625787706314199045100321092773175149680378836678124766164328598941631048157824833933555981490578600657038994486797794996267389
Flag: corctf{i_h0pe_you_3njoyed_p1ecing_t0geth3r_th4t_curv3_puzz1e_:)}
```
## threetreasures - 19 solves
> Let's find the treasures of three amongst the order of three.
Attachments:
1. source.py
```python=
from sage.all import *
from Crypto.Util.number import bytes_to_long, getPrime
from random import getrandbits
from secret import flag, p, x, y
def random_pad(n, length):
return (n << (length - n.bit_length())) + getrandbits(length - n.bit_length())
flag = bytes_to_long(flag)
fbits = flag.bit_length()
piece_bits = fbits // 3
a, b, c = flag >> (2 * piece_bits), (flag >> piece_bits) % 2**piece_bits, flag % 2**piece_bits
print(f'flag bits: {fbits}')
assert p.bit_length() == 512
q = getPrime(512)
n = p * q
ct = pow(random_pad(c, 512), 65537, n)
E = EllipticCurve(GF(p), [a, b])
G = E(x, y)
assert G * 3 == E(0, 1, 0)
print(f"n = {n}")
print(f"ct = {ct}")
print(f"G = {G}")
```
2. output.txt
```
flag bits: 375
n = 97915144495462666300795364589570761584322186881492143950078938328867290046424857019504657598883431857075772605985768551863478086544857915637724181292135280539943713583281151707224031808445390796342632369109562433275679473233398168787639940620683354458292117457239552762694657810883738834935391913698852811737
ct = 20363336204536918055183609604474634074539942561101208682977506918349108499764147141944713060658857301108876346227077713201766486360148051069618774935469969057808945271860025712869868421279488925632657486125211168314387620225601572070169746014988350688548088791906161773656057212229972967244998930356157725393
G = (3115938227771961657567351113281194074601897467086373590156577019504528350118731801249444974253028485083440228959842232653488953448859690520619223338133881 : 2665631524518629436093690344927156713668794128141943350227439039472817541262750706395352700109556004195261826476428128993836186741129487842876154876730189 : 1)
```
Câu này lúc giải đang diễn ra thì mình bị ngáo nên vừa hết giải mới làm ra :)). Lý do là vì mình quên trong phần đầu của flag có chữ `corctf{`, do đó ta đã biết được $55$ bits của $a$ => chỉ cần tìm $70$ bits còn lại của $a$ là được ($a = b = c = 375\div{3} = 125$). Mà mình lại tìm thẳng 125 bits (có vẻ lớn hơn điều kiện của phương pháp **Coppersmith** tìm `small_root`) :crying_cat_face:
Script bằng sagemath:
```python=
from sage.all import *
n = 97915144495462666300795364589570761584322186881492143950078938328867290046424857019504657598883431857075772605985768551863478086544857915637724181292135280539943713583281151707224031808445390796342632369109562433275679473233398168787639940620683354458292117457239552762694657810883738834935391913698852811737
ct = 20363336204536918055183609604474634074539942561101208682977506918349108499764147141944713060658857301108876346227077713201766486360148051069618774935469969057808945271860025712869868421279488925632657486125211168314387620225601572070169746014988350688548088791906161773656057212229972967244998930356157725393
G = (3115938227771961657567351113281194074601897467086373590156577019504528350118731801249444974253028485083440228959842232653488953448859690520619223338133881, 2665631524518629436093690344927156713668794128141943350227439039472817541262750706395352700109556004195261826476428128993836186741129487842876154876730189)
Z=Zmod(n)
xg=Z(G[0])
yg=Z(G[1])
P.<x> = PolynomialRing(Z)
high=int.from_bytes(b'corctf{', 'big') << 70
f = (3*xg^2 + high + x)^2 - 12*xg*yg^2
a=f.small_roots(X=2^70, beta=0.5)[0]
p=gcd(ZZ(f(a)), n)
assert is_prime(p)
a+=high
b = (G[1]^2 - G[0]^3 - a*G[0]) % p
q = n//p
d = inverse_mod(0x10001, (p-1)*(q-1))
m = int(Z(ct)^d)
c = m >> (512 - 125)
flag = (int(a) << (2*125)) + (int(b) << 125) + int(c)
print(flag.to_bytes((flag.bit_length()+7)//8,'big').decode())
```
```
corctf{you_have_conquered_the_order_of_three!!}
```
Hết rồi đó :wave:.Cảm ơn mọi người đã đọc blog về mấy bài CTF về ~~Toán~~ Cryptography nhạt nhẽo của mình :rolling_on_the_floor_laughing: