7/31-8/1 で開催していた Crypto CTF 2021 にソロで参加しました。結果は 7th/444 (得点のあるチームのみカウント) でした。
自分の好きな Crypto のみが出題される CTF だったので結構力を入れて挑みました (早寝早起きした)。Crypto というよりは算数パズルみたいな問題が多かったですが楽しめました。 問題数多いので難易度 medium 以上の問題について writeup を書きます。
medium
Triplet
50 solves
RSA の問題。 ある1つの で が満たされるような3種類の素数ペア を指定します。 を満たす必要があります。
となるような を見つけられれば、 のときに も同時に満たされます。なので がそのようになっている素数を求めていきます。 適当に素数 を生成し、 が素数となるような を探すことで雑に見つかります。
from Crypto.Util.number import isPrime
p_list = [978192613682615126805602952972767216490903041377]
for i in range(2, 100):
tmp = (p_list[-1] - 1) * i + 1
if isPrime(tmp):
p_list.append(tmp)
break
for i in range(2, 1000):
tmp = (p_list[-1] - 1) * i + 1
if isPrime(tmp):
p_list.append(tmp)
break
q = 792815675392945389923804143268131204888157010473
phi_list = [(p - 1) * (q - 1) for p in p_list]
次に かつ となる を雑に探します。
for e in range(2, 10**6):
try:
d = int(pow(e, -1, phi_list[-1]))
except ZeroDivisionError:
continue
if d < phi_list[0]:
print(e, d)
break
これで求まった を送ることでフラグを得ることができました。
from pwn import remote
e, d = (
10391,
288835272238104090924086199222396301800937844500536833016516522864596327429508604217436920083751,
)
_r = remote("07.cr.yp.toc.tf", 18010)
_r.sendlineafter("[Q]uit\n", "s")
for i in range(3):
_ = _r.recvline()
_r.sendline(f"{p_list[i]},{q}")
_ = _r.recvline()
_r.sendline(f"{e},{d}")
_r.interactive()
CCTF{7HrE3_b4Bie5_c4rRi3d_dUr1nG_0Ne_pr39naNcY_Ar3_triplets}
Elegant Curve
16 solves
楕円曲線の問題。
if isPrime(p) and 0 < a < p and 0 < b < p and p.bit_length() == nbit:
if isPrime(q) and 0 < c < q and 0 < d < q and 0 < q - p <= 2022 and q.bit_length() == nbit:
これらを満たす2種類の楕円曲線のパラメータ , を指定します。 nbit
は160です。その楕円曲線上で離散対数問題が解ければフラグが得られます。
まともな楕円曲線だと160bit での離散対数問題を解くのは難しいため、2種類の工夫をしました。 1つ目は の形にしたことです。このような楕円曲線では (ただし ) の写像によって体 上の multiplicative group に移せることが知られています。 2つ目は を小さい整数の積にすることです。これは Pohlig-Hellman algorithm を使えるようにするためです。
これで離散対数問題を十分高速に解くことができるようになるのですが、 の の位数によっては偽の解が生じ得ます (解を位数で割った余りしか得られないため。 の素因数に小さい値があればあるほど偽の解が出やすい)。これはいい回避方法がぱっと思いつかなかったので何回か try することでフラグを得ました。
import re
from pwn import remote, context
def fast_dlp_modn(H, G, n):
return int(pari(f"znlog({H}, Mod({G}, {n}))"))
while True:
_r = remote("07.cr.yp.toc.tf", 10010)
p = 2^2 * 3 * 5 * 149 * 397 * 89431 * 429733 * 4754429087 * 21431031803 * 54082242377 + 1
q = 2^2 * 3 * 47^2 * 341676649 * 21030287449 * 105537162827 * 37391085931811 + 1
a = p - 3
b = p - 2
c = q - 3
d = q - 2
_r.sendlineafter("[Q]uit\n", "s")
_r.sendlineafter(": a, b, p \n", f"{a},{b},{p}")
_r.sendlineafter("<= 2022\n", f"{c},{d},{q}")
ret = _r.recvline().strip().decode()
G = list(map(int, re.findall(r"\| G is on first ECC and G = {\(([0-9]+), ([0-9]+)\)}", ret)[0]))
ret = _r.recvline().strip().decode()
H = list(map(int, re.findall(r"\| H is on second ECC and H = {\(([0-9]+), ([0-9]+)\)}", ret)[0]))
ret = _r.recvline().strip().decode()
U = list(map(int, re.findall(r"\| r \* G = {\(([0-9]+), ([0-9]+)\)}", ret)[0]))
ret = _r.recvline().strip().decode()
V = list(map(int, re.findall(r"\| s \* H = {\(([0-9]+), ([0-9]+)\)}", ret)[0]))
alpha = 691543214394924384546321648306135152969653983571
G_psi = (G[1] + alpha * (G[0] + 1)) * pow(G[1] - alpha * (G[0] + 1), -1, p) % p
U_psi = (U[1] + alpha * (U[0] + 1)) * pow(U[1] - alpha * (U[0] + 1), -1, p) % p
r = fast_dlp_modn(U_psi, G_psi, p)
assert pow(G_psi, r, p) == U_psi
alpha = 522942771760774594392535493944005348637064325562
H_psi = (H[1] + alpha * (H[0] + 1)) * pow(H[1] - alpha * (H[0] + 1), -1, q) % q
V_psi = (V[1] + alpha * (V[0] + 1)) * pow(V[1] - alpha * (V[0] + 1), -1, q) % q
s = fast_dlp_modn(V_psi, H_psi, q)
assert pow(H_psi, s, q) == V_psi
_r.sendlineafter("get the flag: \n", f"{r},{s}")
ret = _r.recvline().strip().decode()
_r.close()
if "flag" in ret:
print(ret)
break
Improved
37 solves
def gen_params(nbit):
p, q = [getPrime(nbit) for _ in range(2)]
n, f, g = p * q, lcm(p - 1, q - 1), p + q
e = pow(g, f, n ** 2)
u = divmod(e - 1, n)[0]
v = inverse(u, n)
params = int(n), int(f), int(v)
return params
このように生成された params に対し、
def improved(m, params):
n, f, v = params
if 1 < m < n ** 2 - 1:
e = pow(m, f, n ** 2)
u = divmod(e - 1, n)[0]
L = divmod(u * v, n)[1]
H = hashlib.sha1(str(L).encode("utf-8")).hexdigest()
return H
で のハッシュが計算されます。このハッシュが衝突する を送ればフラグが得られます。
実は なので が求まるため2次方程式を解くことで が求まったり、 から が求まったりします。
しかし なので2の倍数であることが確実なため、 と を指定するだけで が同じになり、ハッシュを衝突させることができます。何を問われた問題だったのだろう🤔
import re
from Crypto.Util.number import getPrime, inverse
from pwn import remote
_r = remote("05.cr.yp.toc.tf", 14010)
_r.sendlineafter("[Q]uit\n", "g")
ret = _r.recvline().strip().decode()
n, f, v = map(int, re.findall(r"\| Parameters = \(([0-9]+), ([0-9]+), ([0-9]+)\)", ret)[0])
_r.sendlineafter("[Q]uit\n", "r")
_r.sendlineafter("| please send the messages split by comma: \n", f"{2},{n**2-2}")
_r.interactive()
CCTF{Phillip_N0W_4_pr0b4b1liStiC__aSymM3Tr1C__AlGOrithM!!}
Ferman
31 solves
e = 65537
isPrime(p) = True
isPrime(q) = True
n = p * q
(p - 437)**2 + (q - 784)**2 = 10361260280629817716615376756017077930645839952216327941760067320726021213209057344786508377349803583538968917852066194184384961070812319588143033971547102366229627802808807925134421089388501061177731372428832404144683890221704206802913372432409131676725210901535098751418251872866013280844158228683928132748670905650514575544930537361662019547998142569015889799098700356406386839808082802819221420083338846406259588746394272324929265325919199250529991846741612256230795883813515625
m = bytes_to_long(flag)
c = pow(m, e, n)
RSA の秘密鍵 に対し、 が成立するという条件が与えられます。
ガウス整数の素因数分解を考えることで、 となる を求めることができたため、これを利用します。
k = 10361260280629817716615376756017077930645839952216327941760067320726021213209057344786508377349803583538968917852066194184384961070812319588143033971547102366229627802808807925134421089388501061177731372428832404144683890221704206802913372432409131676725210901535098751418251872866013280844158228683928132748670905650514575544930537361662019547998142569015889799098700356406386839808082802819221420083338846406259588746394272324929265325919199250529991846741612256230795883813515625
GI = GaussianIntegers()
GI(k).factor()
(I) * (-110935748565706808545452731038884*I - 1673809851384879136750725022885175)^7 * (110935748565706808545452731038884*I - 1673809851384879136750725022885175)^7 * (I - 6)^7 * (-I - 2)^7 * (2*I + 1)^7 * (I + 6)^7
(-110935748565706808545452731038884*I - 1673809851384879136750725022885175)^7 * (I - 6)^7 * (2*I +1)^7
2250191360275908942759836805214916387730617773023826091034265686964815046012130214759405715974002077792077919656432366401968366052803784066989682480353264274699925759660197732461666118233606279297721205467765814980690185654667097229297968283*I + 2301716560041542621183488933524077475239162882878719181927983642214147903067475044730115988538550222999449838267481218690409785448038694891086920968207750944411381429555732054836934832649287381942584929838820786450610981028633759669457251244
a = 2250191360275908942759836805214916387730617773023826091034265686964815046012130214759405715974002077792077919656432366401968366052803784066989682480353264274699925759660197732461666118233606279297721205467765814980690185654667097229297968283
b = 2301716560041542621183488933524077475239162882878719181927983642214147903067475044730115988538550222999449838267481218690409785448038694891086920968207750944411381429555732054836934832649287381942584929838820786450610981028633759669457251244
assert a ** 2 + b ** 2 == k
あとは RSA の復号を行うだけです。
enc_flag = 275368327318108903295175979235078859662726052499685937295398091157377276372757798047586402720819472986388661996446380988082266671821826476798218433162440327531549083297724499051700420109280345499960900696603611709412955636174302937617820524571024304344901792165038939427510954561450506468719265498723674829197024796448390659474630664539875687968121994396242843446045450727872511862272187355405758196007422012606406934513453435705131334147064102151401784980611761888867009132682337
p = b + 437
q = a + 784
phi = (p - 1) * (q - 1)
d = int(pow(65537, -1, phi))
flag = pow(enc_flag, d, p * q)
print(long_to_bytes(flag))
CCTF{Congrats_Y0u_5OLv3d_x**2+y**2=z**7}
Salt and Pepper
69 solves
assert len(salt) == len(pepper) == 19
assert md5(salt).hexdigest() == "5f72c4360a2287bc269e0ccba6fc24ba"
assert sha1(pepper).hexdigest() == "3e0d000a4b0bd712999d730bc331f400221008e0"
def auth_check(salt, pepper, username, password, h):
return (
sha1(
pepper + password + md5(salt + username).hexdigest().encode("utf-8")
).hexdigest()
== h
)
salt
や pepper
のハッシュがわかっているときに、ある特定の文字列を含んでいる username
password
を指定したときに auth_check
が通るようなハッシュを求める問題です。 length extension attack が使えます。 length extension attack には hashpump を使用しました。
hashpump -s 5f72c4360a2287bc269e0ccba6fc24ba -k 19 -a n3T4Dm1n
95623660d3d04c7680a52679e35f041c
\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98\x00\x00\x00\x00\x00\x00\x00n3T4Dm1n
hashpump -s 3e0d000a4b0bd712999d730bc331f400221008e0 -k 19 -a P4s5W0rd95623660d3d04c7680a52679e35f041c
83875efbe020ced3e2c5ecc908edc98481eba47f
\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98P4s5W0rd95623660d3d04c7680a52679e35f041c
これらをサーバーに送ることでフラグが得られました。
from hashlib import md5, sha1
from Crypto.Util.number import long_to_bytes, bytes_to_long
from pwn import remote
inp_username = b"\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98\x00\x00\x00\x00\x00\x00\x00n3T4Dm1n"
inp_password = b"\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98P4s5W0rd"
sha1_hash = "83875efbe020ced3e2c5ecc908edc98481eba47f".encode()
_r = remote("02.cr.yp.toc.tf", 28010)
_r.sendlineafter("[Q]uit\n", "l")
_r.sendlineafter(
"| send your username, password as hex string separated with comma: \n",
inp_username.hex() + "," + inp_password.hex(),
)
_r.sendlineafter("| send your authentication hash: \n", sha1_hash)
_r.interactive()
CCTF{Hunters_Killed_82%_More_Wolves_Than_Quota_Allowed_in_Wisconsin}
Tiny ECC
16 solves
Elegant Curve と似た問題。
if a*b != 0:
if isPrime(p) and p.bit_length() == nbit and isPrime(2*p + 1):
これらを満たす を指定したあと、楕円曲線 上と 上の楕円曲線 で離散対数問題が解ければフラグが得られます。
の条件がゆるく、 とすれば楕円曲線は となります。このような singular curve 上では の写像によって additive group に移せることが知られています。
import re
from Crypto.Util.number import inverse
from pwn import remote, context
# 適当に決めた
p = 210954976541768303162895189242638938551
q = 421909953083536606325790378485277877103
_r = remote("01.cr.yp.toc.tf", 29010)
_r.sendlineafter("[Q]uit\n", "c")
_r.sendlineafter("send your prime: \n", str(p))
_r.sendlineafter("[Q]uit\n", "a")
_r.sendlineafter("a and b separated by comma: \n", f"{p*q},{p*q}")
_r.sendlineafter("[Q]uit\n", "s")
_ = _r.recvuntil("| We know that: \n")
ret = _r.recvline().strip().decode()
Px, Py = map(int, re.findall(r"\| P = \(([0-9]+),([0-9]+)\)", ret)[0])
ret = _r.recvline().strip().decode()
kPx, kPy = map(int, re.findall(r"\| k\*P = \(([0-9]+),([0-9]+)\)", ret)[0])
ret = _r.recvline().strip().decode()
Qx, Qy = map(int, re.findall(r"\| Q = \(([0-9]+),([0-9]+)\)", ret)[0])
ret = _r.recvline().strip().decode()
lQx, lQy = map(int, re.findall(r"\| l\*Q = \(([0-9]+),([0-9]+)\)", ret)[0])
k = inverse(Px * inverse(Py, p) % p, p) * kPx * inverse(kPy, p) % p
l = inverse(Qx * inverse(Qy, q) % q, q) * lQx * inverse(lQy, q) % q
_r.sendlineafter("send the k and l separated by comma: \n", f"{k},{l}")
_r.interactive()
CCTF{ECC_With_Special_Prime5}
Onlude
48 solves
フラグの値を陽に含んでいる11x11の行列に対し、 という計算で暗号化されています。ここでは をLU分解したもので、 が成立します。与えられている値はです。
がちゃがちゃ式変形をすることで が求まります。
- (LUL)^{-1}
p = 71
alphabet = "=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$!?_{}<>"
def load_matrix(s):
s_mod = s.replace("[ ", "[").replace(" ", ",").replace(" ", ",")
return matrix(GF(p), list(map(eval, s_mod.split("\n"))))
E = load_matrix(
"""[25 55 61 28 11 46 19 50 37 5 21]
[20 57 39 9 25 37 63 31 70 15 47]
[56 31 1 1 50 67 38 14 42 46 14]
[42 54 38 22 19 55 7 18 45 53 39]
[55 26 42 15 48 6 24 4 17 60 64]
[ 1 38 50 10 19 57 26 48 6 4 14]
[13 4 38 54 23 34 54 42 15 56 29]
[26 66 8 48 6 70 44 8 67 68 65]
[56 67 49 61 18 34 53 21 7 48 32]
[15 70 10 34 1 57 70 27 12 33 46]
[25 29 20 21 30 55 63 49 11 36 7]"""
)
LUL = load_matrix(
"""[50 8 21 16 13 33 2 12 35 20 14]
[36 55 36 34 27 28 23 21 62 17 8]
[56 26 49 39 43 30 35 46 0 58 43]
[11 25 25 35 29 0 22 38 53 51 58]
[34 14 69 68 5 32 27 4 27 62 15]
[46 49 36 42 26 12 28 60 54 66 23]
[69 55 30 65 56 13 14 36 26 46 48]
[25 48 16 20 34 57 64 62 61 25 62]
[68 39 11 40 25 11 7 40 24 43 65]
[54 20 40 59 52 60 37 14 32 44 4]
[45 20 7 26 45 45 50 17 41 59 50]"""
)
L1S2L = load_matrix(
"""[34 12 70 21 36 2 2 43 7 14 2]
[ 1 54 59 12 64 35 9 7 49 11 49]
[69 14 10 19 16 27 11 9 26 10 45]
[70 17 41 13 35 58 19 29 70 5 30]
[68 69 67 37 63 69 15 64 66 28 26]
[18 29 64 38 63 67 15 27 64 6 26]
[ 0 12 40 41 48 30 46 52 39 48 58]
[22 3 28 35 55 30 15 17 22 49 55]
[50 55 55 61 45 23 24 32 10 59 69]
[27 21 68 56 67 49 64 53 42 46 14]
[42 66 16 29 42 42 23 49 43 3 23]"""
)
R1S8 = load_matrix(
"""[51 9 22 61 63 14 2 4 18 18 23]
[33 53 31 31 62 21 66 7 66 68 7]
[59 19 32 21 13 34 16 43 49 25 7]
[44 37 4 29 70 50 46 39 55 4 65]
[29 63 29 43 47 28 40 33 0 62 8]
[45 62 36 68 10 66 26 48 10 6 61]
[43 30 25 18 23 38 61 0 52 46 35]
[ 3 40 6 45 20 55 35 67 25 14 63]
[15 30 61 66 25 33 14 20 60 50 50]
[29 15 53 22 55 64 69 56 44 40 8]
[28 40 69 60 28 41 9 14 29 4 29]"""
)
S2 = LUL * L1S2L * LUL.inverse()
S8 = S2 ** 4
R = S8 * R1S8.inverse()
L1S = L1S2L * LUL.inverse()
A = L1S.inverse() * E - R
A_list = A.list()
flag = ""
for a in A_list[:120:5]:
flag += alphabet[a]
CCTF{LU__D3c0mpO517Ion__4L90?}
Tuti
71 solves
フラグの前半部分、後半部分をそれぞれ (整数表示) として、ある与えられた に対し
assert((x**2 + 1)*(y**2 + 1) - 2*(x - y)*(x*y - 1) == 4*(int(k, 16) + x*y))
が成立することがわかっています。
雑に sagemath の solve
に投げてみます。
k = int(
"""
000bfdc32162934ad6a054b4b3db8578674e27a165113f8ed018cbe9112
4fbd63144ab6923d107eee2bc0712fcbdb50d96fdf04dd1ba1b69cb1efe
71af7ca08ddc7cc2d3dfb9080ae56861d952e8d5ec0ba0d3dfdf2d12764
""".replace(
"\n", ""
),
16,
)
x, y = var("x, y")
f = (x^2 + 1) * (y^2 + 1) - 2 * (x - y) * (x * y - 1) - 4 * (k + x * y)
roots = solve(f == 0, [x, y])
が正であることから、
x == -(r2 - 992752253935874779143952218845275961347009322164731344882417010624071055636710540798045985678351986133613)/(r2 - 1), y == r2
と表されることがわかります。 と変数を変えると、 は
992752253935874779143952218845275961347009322164731344882417010624071055636710540798045985678351986133612
で割り切れる必要があります。 この整数を素因数分解し、それぞれの素因数がのどちらのものかを全探索させるコードを書き、フラグを復元しました。
primes = [2, 2, 3, 11, 11, 19, 47, 71, 3449, 11953, 5485619, 2035395403834744453, 17258104558019725087, 1357459302115148222329561139218955500171643099]
for i in range(2**len(primes)):
n = i
tmp_primes = []
for j in range(len(primes)):
if n % 2 == 1:
tmp_primes.append(primes[j])
n //= 2
s = prod(tmp_primes)
flag_suf = long_to_bytes(s + 1)
flag_pre = long_to_bytes(992752253935874779143952218845275961347009322164731344882417010624071055636710540798045985678351986133612 // s - 1)
if b"CCTF" in flag_pre:
print(flag_pre + flag_suf)
CCTF{S1mPL3_4Nd_N!cE_Diophantine_EqUa7I0nS!}
Maid
36 solves
ある公開鍵 に対して、 の暗号化が何回でもできます。またソースコードには計算方法が書かれていないのですが復号もできます。フラグを で暗号化したものが与えられるのでこれを復号する問題です。
復号の処理を探るためいろいろ実験を行ったところ、 となっていることがわかりました (なんで)。怪しいので を計算してみると が求まりました (なんで)。何もわからない。
import re
from Crypto.Util.number import long_to_bytes
from pwn import remote
_r = remote("04.cr.yp.toc.tf", 38010)
# leak pubkey
_r.sendlineafter("| [Q]uit\n", "e")
_r.sendlineafter("| Send the message to encrypt: \n", str(2**1536))
ret = _r.recvline().strip().decode()
ret_int = int(re.findall(r"\| encrypt\(msg, pubkey\) = (.*)", ret)[0])
pubkey = 2 ** 3072 - ret_int
# leak p
_r.sendlineafter("| [Q]uit\n", "d")
_r.sendlineafter("| Send the ciphertext to decrypt: \n", str(2))
ret = _r.recvline().strip().decode()
ret_int = int(re.findall(r"\| decrypt\(enc, privkey\) = (.*)", ret)[0])
p = int(sqrt(gcd(ret_int ** 2 - 2, pubkey)))
q = pubkey // p ** 2
assert p ** 2 * q == pubkey
phi = (p - 1) * (q - 1)
d = int(pow(65537, -1, phi))
# decode enc flag
_r.sendlineafter("| [Q]uit\n", "s")
ret = _r.recvline().strip().decode()
ret_int = int(re.findall(r"\| enc = (.*)", ret)[0])
flag = int(pow(ret_int, d, p * q))
print(long_to_bytes(flag))
CCTF{___Ra8!N_H_Cryp70_5YsT3M___}
medium-hard
Double Miff
16 solves
def onmiff(a, b, p, G):
x, y = G
return (a * x * (y ** 2 - 1) - b * y * (x ** 2 - 1)) % p == 0
このような曲線 () を考えます。フラグの前半部分、後半部分を 座標に持つこの曲線上の点を として、 の座標が与えられています。ここでこの曲線上の足し算は
def addmiff(X, Y):
x_1, y_1 = X
x_2, y_2 = Y
x_3 = (
(x_1 + x_2)
* (1 + y_1 * y_2)
* inverse((1 + x_1 * x_2) * (1 - y_1 * y_2), p)
% p
)
y_3 = (
(y_1 + y_2)
* (1 + x_1 * x_2)
* inverse((1 + y_1 * y_2) * (1 - x_1 * x_2), p)
% p
)
return (x_3, y_3)
で定義されます。
の3点が与えられているので、この曲線のパラメータ が求められないかを考えます。 がちゃがちゃ式変形をすると、ある2点 の 座標について、
x0 * (y0 ^ 2 - 1) * y1 * (x1 ^ 2 - 1) - x1 * (y1 ^ 2 - 1) * y0 * (x0 ^ 2 - 1)
が の倍数となることがわかります。これを3点から3通り求め gcd を取り、さらに素因数分解を行うことで が求まります。 が求まれば連立方程式から も求まりますが、この方程式の解は不定となり、 となる が求まります。簡単のため とします。
次に や の座標を求めるため、 としたときに を計算することで についての方程式を立てます。煩雑なので省略しますが、
が成り立ちます。
あとはこの4次方程式を解けばフラグが得られます。
PQ = (
540660810777215925744546848899656347269220877882,
102385886258464739091823423239617164469644309399,
)
QQ = (
814107817937473043563607662608397956822280643025,
961531436304505096581595159128436662629537620355,
)
PP = (
5565164868721370436896101492497307801898270333,
496921328106062528508026412328171886461223562143,
)
p = 1141623079614587900848768080393294899678477852887
c = int(x0 * (y0 ^ 2 - 1) * pow(y0 * (x0 ^ 2 - 1), -1, p) % p)
assert c == x1 * (y1 ^ 2 - 1) * pow(y1 * (x1 ^ 2 - 1), -1, p) % p
a = 1
b = c
PR.<x> = PolynomialRing(Zmod(p))
cpp = int((PP[0] * PP[1]) * pow(4, -1, p))
f = (x^2 - 1)^2 * pow(a, -1, p) * b * cpp - x^2
roots = f.roots()
for root, _ in roots:
print(long_to_bytes(root))
cqq = int((QQ[0] * QQ[1]) * pow(4, -1, p))
f = (x^2 - 1)^2 * pow(a, -1, p) * b * cqq - x^2
roots = f.roots()
for root, _ in roots:
print(long_to_bytes(root))
CCTF{D39enEr47E_ECC_4TtaCk!_iN_Huffs?}
Frozen
29 solves
あるパラメータ ( は素数) が与えられ、 の5種類について (はランダムな整数) の前半96bitが与えられています。後半32bitは秘密鍵になっており sign
に使われます。
指定された文字列について sign
を求められればフラグが得られます。
sign
は
def sign(msg, privkey, d):
msg = msg.encode("utf-8")
l = len(msg) // 4
M = [bytes_to_long(msg[4 * i : 4 * (i + 1)]) for i in range(l)]
q = int(next_prime(max(M)))
sign = [M[i] * privkey[i] % q for i in range(l)]
return sign
となっており、実質的に秘密鍵を求めればよさそうです。
問題の性質上 LLL が使えそうです。https://github.com/rkm0959/Inequality_Solving_with_CVP を使わせていただきました。
import copy
import re
from sage.modules.free_module_integer import IntegerLattice
from Crypto.Util.number import bytes_to_long
from pwn import remote
# Directly taken from rbtree's LLL repository
# From https://oddcoder.com/LOL-34c3/, https://hackmd.io/@hakatashi/B1OM7HFVI
def Babai_CVP(mat, target):
M = IntegerLattice(mat, lll_reduce=True).reduced_basis
G = M.gram_schmidt()[0]
diff = target
for i in reversed(range(G.nrows())):
diff -= M[i] * ((diff * G[i]) / (G[i] * G[i])).round()
return target - diff
def solve(mat, lb, ub, weight=None):
num_var = mat.nrows()
num_ineq = mat.ncols()
max_element = 0
for i in range(num_var):
for j in range(num_ineq):
max_element = max(max_element, abs(mat[i, j]))
if weight == None:
weight = num_ineq * max_element
# sanity checker
if len(lb) != num_ineq:
print("Fail: len(lb) != num_ineq")
return
if len(ub) != num_ineq:
print("Fail: len(ub) != num_ineq")
return
for i in range(num_ineq):
if lb[i] > ub[i]:
print("Fail: lb[i] > ub[i] at index", i)
return
# heuristic for number of solutions
DET = 0
if num_var == num_ineq:
DET = abs(mat.det())
num_sol = 1
for i in range(num_ineq):
num_sol *= ub[i] - lb[i]
if DET == 0:
print("Zero Determinant")
else:
num_sol //= DET
# + 1 added in for the sake of not making it zero...
print("Expected Number of Solutions : ", num_sol + 1)
# scaling process begins
max_diff = max([ub[i] - lb[i] for i in range(num_ineq)])
applied_weights = []
for i in range(num_ineq):
ineq_weight = weight if lb[i] == ub[i] else max_diff // (ub[i] - lb[i])
applied_weights.append(ineq_weight)
for j in range(num_var):
mat[j, i] *= ineq_weight
lb[i] *= ineq_weight
ub[i] *= ineq_weight
# Solve CVP
target = vector([(lb[i] + ub[i]) // 2 for i in range(num_ineq)])
result = Babai_CVP(mat, target)
for i in range(num_ineq):
if (lb[i] <= result[i] <= ub[i]) == False:
print("Fail : inequality does not hold after solving")
break
# recover x
fin = None
if DET != 0:
mat = mat.transpose()
fin = mat.solve_right(result)
## recover your result
return result, applied_weights, fin
def sign(msg, privkey, d):
msg = msg.encode("utf-8")
l = len(msg) // 4
M = [bytes_to_long(msg[4 * i : 4 * (i + 1)]) for i in range(l)]
q = int(next_prime(max(M)))
sign = [M[i] * privkey[i] % q for i in range(l)]
return sign
_r = remote("03.cr.yp.toc.tf", 25010)
_r.sendlineafter("[Q]uit\n", "s")
_ = _r.recvuntil("p = ")
p = int(_r.recvline())
_ = _r.recvuntil("r = ")
r = int(_r.recvline())
_r.sendlineafter("[Q]uit\n", "p")
ret = _r.recvline().strip().decode()
pubkey = list(
map(
int,
re.findall(
r"pubkey = \[([0-9]+), ([0-9]+), ([0-9]+), ([0-9]+), ([0-9]+)\]", ret
)[0],
)
)
_r.sendlineafter("[Q]uit\n", "e")
ret = _r.recvline().strip().decode()
rand_msg = re.findall(r"\| the signature for \"(.*)\" is :", ret)[0]
ret = _r.recvline().strip().decode()
rand_sign = list(
map(
int,
re.findall(
r"\| signature = \[([0-9]+), ([0-9]+), ([0-9]+), ([0-9]+), ([0-9]+)\]", ret
)[0],
)
)
# [pub0, ..., pub4, priv0, ..., priv4, s]
mat = matrix(ZZ, 11, 10)
for i in range(5):
mat[i, i] = -1
mat[5 + i, i] = -p
mat[10, i] = r ** (i + 1)
mat[i, 5 + i] = 1
lb = pubkey + [0] * 5
ub = pubkey + [2 ** 32] * 5
ret = solve(copy.deepcopy(mat), copy.deepcopy(lb), copy.deepcopy(ub))
privkey = list(ret[0][5:10])
# check
dbit = 32
assert sign(rand_msg, privkey, dbit) == rand_sign
_r.sendlineafter("[Q]uit\n", "f")
ret = _r.recvline().strip().decode()
rand_msg = re.findall(r"\| send the signature of the following message like example: (.*)", ret)[0]
sign = sign(rand_msg, privkey, dbit)
_r.sendline(",".join(map(str, sign)))
_r.interactive()
CCTF{Lattice_bA5eD_T3cHn1QuE_70_Br34K_LCG!!}
Wolf
33 solves
AES の問題。AES の key
が与えられており、 GCM モードでフラグが暗号化されます。 nonce
は
niv = os.urandom(random.randint(1, 11))
で生成されます。
niv
の文字列長が2以下であれば十分高速に niv
を全探索できます。さらに文字列長が2以下である確率は2/11であり、十分に高いです。下のスクリプトを何度か回してフラグを得ました。
import re
from itertools import product
from binascii import unhexlify
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes
from pwn import remote
passphrase = b'HungryTimberWolf'
_r = remote("01.cr.yp.toc.tf", 27010)
_r.sendlineafter("[Q]uit\n", "g")
ret = _r.recvline().strip().decode()
enc_flag = unhexlify(re.findall(r"\| encrypt\(flag\) = (.*)", ret)[0])
for n in range(1, 3):
for i_list in product(range(256), repeat=n):
tmp_niv = b"".join(map(long_to_bytes, i_list))
tmp_aes = AES.new(passphrase, AES.MODE_GCM, nonce=tmp_niv)
tmp_flag = tmp_aes.decrypt(enc_flag)
if b"CCTF{" in tmp_flag:
print(tmp_flag)
CCTF{____w0lveS____c4n____be____dan9er0uS____t0____p3oplE____!!!!!!}
LINDA
23 solves
ある固定の素数 (given) でパラメータ が以下のように生成されます (これらも given)。
def keygen(p):
while True:
u = getRandomRange(1, p)
if pow(u, (p - 1) // 2, p) != 1:
break
x = getRandomRange(1, p)
w = pow(u, x, p)
while True:
r = getRandomRange(1, p - 1)
if gcd(r, p - 1) == 1:
y = x * inverse(r, p - 1) % (p - 1)
v = pow(u, r, p)
return u, v, w
これらを使ってフラグ () が以下のように暗号化されます。
def encrypt(m, pubkey):
p, u, v, w = pubkey
assert m < p
r, s = [getRandomRange(1, p) for _ in "01"]
ca = pow(u, r, p)
cb = pow(v, s, p)
cc = m * pow(w, r + s, p) % p
enc = (ca, cb, cc)
return enc
encrypt
内で生成されている がわかればフラグを復号できそうです。
を試しに素因数分解してみると小さい素数の積になっていることがわかりました。つまり Pohlig-Hellman algorithm が使えます。
from Crypto.Util.number import long_to_bytes
# 一度接続して p, u, v, w を入手
p = 80040674322486200183331704165146058688355128781788651227350875871674742319920911360783561260194150493874495739587048685899899944078938543685265460797023
u = 14108147438067096264181521725638271783716062505015085501011997363539105529220134686668833616162025711494007957907693959413538566683505186659769015857877
v = 16000409593087500438791179247115777574915938869122915417574163962787315162920602492446105732367216596908836487469326834168577373919395024737878728742484
w = 36011874435065497442299962693196160030321693654149785514569095639364173271601769821987901048673065443159944404388580038382010555730000137869326611006262
enc = (51546876314099158815771406875395450944729146278198246293217184833201398085891945591409346537984132235322811468863166962330212626988458892438754396495963, 56446131178709957909815173502301466085398638405195449492108949845556028028436338736444947623899626798956388201052886950557276582688956351454340069032396, 10555296210928660270792188233294969486948538480518952008230817142909325378503829670147773076742266881530756838661770694462905599485290318088924705105178)
Z = Zmod(p)
r = int(discrete_log(Z(enc[0]), Z(u)))
s = int(discrete_log(Z(enc[1]), Z(v)))
print(long_to_bytes(pow(Z(w) ** (r + s), -1, p) * enc[2]))
CCTF{1mPr0v3D_CrYp7O_5yST3m_8Y_Boneh_Boyen_Shacham!}
RSAphantine
29 solves
2*z**5 - x**3 + y*z = 47769864706750161581152919266942014884728504309791272300873440765010405681123224050402253883248571746202060439521835359010439155922618613520747411963822349374260144229698759495359592287331083229572369186844312169397998958687629858407857496154424105344376591742814310010312178029414792153520127354594349356721
x**4 + y**5 + x*y*z = 89701863794494741579279495149280970802005356650985500935516314994149482802770873012891936617235883383779949043375656934782512958529863426837860653654512392603575042842591799236152988759047643602681210429449595866940656449163014827637584123867198437888098961323599436457342203222948370386342070941174587735051
y**6 + 2*z**5 + z*y = 47769864706750161581152919266942014884728504309791272300873440765010405681123224050402253883248571746202060439521835359010439155922618613609786612391835856376321085593999733543104760294208916442207908167085574197779179315081994735796390000652436258333943257231020011932605906567086908226693333446521506911058
p = nextPrime(x**2 + z**2 + y**2 << 76)
q = nextPrime(z**2 + y**3 - y*x*z ^ 67)
n, e = p * q, 31337
m = bytes_to_long(FLAG)
c = pow(m, e, n)
c = 486675922771716096231737399040548486325658137529857293201278143425470143429646265649376948017991651364539656238516890519597468182912015548139675971112490154510727743335620826075143903361868438931223801236515950567326769413127995861265368340866053590373839051019268657129382281794222269715218496547178894867320406378387056032984394810093686367691759705672
以上の情報だけが与えられます… 最初の3式から が求まれば が求まり、フラグを復号できそうです。
3式を上から順に式1, 2, 3と呼ぶことにし、右辺の数値をそれぞれ とします。 式3 - 式1 を考えると、 となります。 を素因数分解してみると
3133713317731333 * 28413320364759425177418147555143516002041291710972733253944530195017276664069717887927099709630886727522090965378073004342203980057853092114878433424202989
となっていました。 であることから
x + y^2 == 3133713317731333
x^2 - xy^2 + y^4 == 28413320364759425177418147555143516002041291710972733253944530195017276664069717887927099709630886727522090965378073004342203980057853092114878433424202989
がわかります。これらを連立して解くことで が求まり、式1-3からも求まります。
a = 3133713317731333
"""
# y を以下の式で求めた
y = var("y")
solve(y^4 - 2 * a * y^2 + a^2 + y^4 - a * y^2 + y^4 - 28413320364759425177418147555143516002041291710972733253944530195017276664069717887927099709630886727522090965378073004342203980057853092114878433424202989, y)
"""
y = 311960913464334198969500852124413736815
x = a - y^2
z = (89701863794494741579279495149280970802005356650985500935516314994149482802770873012891936617235883383779949043375656934782512958529863426837860653654512392603575042842591799236152988759047643602681210429449595866940656449163014827637584123867198437888098961323599436457342203222948370386342070941174587735051 - x^4 - y^5) // x // y
p = next_prime(x**2 + z**2 + y**2 << 76)
q = next_prime(z**2 + y**3 - y*x*z ^^ 67)
n = p * q
phi = (p - 1) * (q - 1)
d = int(pow(e, -1, phi))
pow(c, d, n)
CCTF{y0Ur_jO8_C4l13D_Diophantine_An4LySI5!}
hard
Rohald
21 solves
ison
, teal
, peam
の実装を見るに、 Edwards curve のようです。Edwards curve のパラメータは不明です。フラグの前半部分、後半部分を としたとき、 Edwards curve 上の について、 が与えられています。
Double Miff と同様に、 の4点から Edwards curve のパラメータを求めます。煩雑な計算なので省略しますが、 が求まります。
求まったパラメータを元に Edwards curve の位数の素因数分解を計算すると、小さい素数の積であり、 Pohlig-Hellman algorithm を使って離散対数問題を解くことができることがわかります。
from Crypto.Util.number import long_to_bytes
P = (398011447251267732058427934569710020713094, 548950454294712661054528329798266699762662)
Px, Py = P
Q = (139255151342889674616838168412769112246165, 649791718379009629228240558980851356197207)
Qx, Qy = Q
S = (730393937659426993430595540476247076383331, 461597565155009635099537158476419433012710)
Sx, Sy = S
T = (500532897653416664117493978883484252869079, 620853965501593867437705135137758828401933)
Tx, Ty = T
p = 903968861315877429495243431349919213155709
c2 = Zmod(p)(int(((x0^2+y0^2)*x1^2*y1^2 - (x1^2+y1^2)*x0^2*y0^2) * pow(x1^2*y1^2 - x0^2*y0^2, -1, p)))
d = int((x0^2 + y0^2 - c2) * pow(c2*x0^2*y0^2, -1, p))
for c in sqrt(c2, all=True):
Pw = (c^2 * d * P[0]^2 - 1) * P[1] % p
Px = -2 * c * (Pw - c) * pow(P[0], -2, p) % p
Py = (4 * c^2 * (Pw - c) + 2 * c * (c^4 * d + 1) * P[0]^2) * pow(P[0], -3, p)
Sw = (c^2 * d * S[0]^2 - 1) * S[1] % p
Sx = -2 * c * (Sw - c) * pow(S[0], -2, p) % p
Sy = (4 * c^2 * (Sw - c) + 2 * c * (c^4 * d + 1) * S[0]^2) * pow(S[0], -3, p)
Qw = (c^2 * d * Q[0]^2 - 1) * Q[1] % p
Qx = -2 * c * (Qw - c) * pow(Q[0], -2, p) % p
Qy = (4 * c^2 * (Qw - c) + 2 * c * (c^4 * d + 1) * Q[0]^2) * pow(Q[0], -3, p)
Tw = (c^2 * d * T[0]^2 - 1) * T[1] % p
Tx = -2 * c * (Tw - c) * pow(T[0], -2, p) % p
Ty = (4 * c^2 * (Tw - c) + 2 * c * (c^4 * d + 1) * T[0]^2) * pow(T[0], -3, p)
PR.<x, y> = PolynomialRing(GF(p))
f = y^2 - (x - c^4*d - 1) * (x^2 - 4*c^4*d)
EC = EllipticCurve(f)
P_EC = EC(Px, Py)
S_EC = EC(Sx, Sy)
Q_EC = EC(Qx, Qy)
T_EC = EC(Tx, Ty)
s = P_EC.discrete_log(S_EC)
t = Q_EC.discrete_log(T_EC)
print(long_to_bytes(s) + long_to_bytes(t))
CCTF{nOt_50_3a5Y_Edw4rDs_3LlipT!c_CURv3}
Trunc
7 solves
ある指定されたメッセージの署名をつくる問題 (そのメッセージで署名はできません)。署名と検証は以下のように行われます。
def sign(msg, keypair):
nbit, dbit = 256, 25
pubkey, privkey = keypair
privkey_bytes = long_to_bytes(privkey)
x = int(sha256(privkey_bytes).hexdigest(), 16) % 2 ** dbit
while True:
k, l = [(getRandomNBitInteger(nbit) << dbit) + x for _ in "01"]
u, v = (k * G).x(), (l * G).y()
if u + v > 0:
break
h = int(sha256(msg).hexdigest(), 16)
s = inverse(k, n) * (h * u - v * privkey) % n
return (int(u), int(v), int(s))
def verify(msg, pubkey, sig):
if any(x < 1 or x >= n for x in sig):
return False
u, v, s = sig
h = int(sha256(msg).hexdigest(), 16)
k, l = h * u * inverse(s, n), v * inverse(s, n)
X = (k * G + (n - l) * pubkey).x()
return (X - u) % n == 0
署名は ElGamal に似ています。 を乱数で生成して 、さらに を計算します。ここで はメッセージのハッシュ、 は秘密鍵です。 が署名です。 ElGamal や DSA の署名は2変数であるのに対しこの問題では3変数なため、署名をいい感じに改変できそうな予感がします。
ある既知メッセージ の署名を とし、署名をつくりたいメッセージを とします。
と変形できるため、 , , とすれば の署名として有効なものになります。
import re
from hashlib import sha256
import ecdsa
from Crypto.Util.number import bytes_to_long
from pwn import remote
E = ecdsa.SECP256k1
G, n = E.generator, E.order
p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
cryptonym = b'Persian Gulf'
_r = remote("07.cr.yp.toc.tf", 23010)
_r.sendlineafter("[Q]uit\n", "p")
ret = _r.recvline().strip().decode()
Hx, Hy = map(int, re.findall(r"\| pubkey = ([0-9]+) ([0-9]+)", ret)[0])
_r.sendlineafter("[Q]uit\n", "s")
_r.sendlineafter("to sign: \n", "00")
ret = _r.recvline().strip().decode()
u0, v0, s0 = map(int, re.findall(r"\| sign = \(([0-9]+), ([0-9]+), ([0-9]+)\)", ret)[0])
h_0 = bytes_to_long(sha256(b"\x00").digest())
h = bytes_to_long(sha256(cryptonym).digest())
s = int(s0 * h * pow(h_0, -1, n) % n)
v = int(v0 * h * pow(h_0, -1, n) % n)
u = u0
_r.sendlineafter("[Q]uit\n", "v")
_r.sendlineafter("to verify: \n", cryptonym.hex())
_r.sendlineafter("with comma: \n", f"{u},{v},{s}")
_r.interactive()
CCTF{__ECC_Bi4seD_N0nCE_53ns3_LLL!!!}