1. はじめに
2022/7/15(金) 23:00 JST ~ 7/16(土) 23:00 JSTに開催された「4th Crypto CTF 2022」にチーム「N30Z30N」でソロ参加し、"Mic Check"問以外で18問を解き、1782点(得点を得た421チーム中23位)を獲得しました。
Crypto CTF には毎年出場していますが、自分にとって比較的解きやすい問題が多数出題されているので、ソロで出て 24 時間頑張れる感じです。反省点としては、難易度 hard の問題をすべて残してしまった(トライすらしなかった)点です。
以下、簡単にですが(難易度易しい順、solve数多い順に) Writeupを記します。
2. Writeup
2-1. Baphomet (難易度:easy, 56 points, 93 solves)
☆設問
以下のスクリプトと、暗号化データの入った「flag.enc」が与えられます。
#!/usr/bin/env python3
from base64 import b64encode
from flag import flag
def encrypt(msg):
ba = b64encode(msg.encode('utf-8'))
baph, key = '', ''
for b in ba.decode('utf-8'):
if b.islower():
baph += b.upper()
key += '0'
else:
baph += b.lower()
key += '1'
baph = baph.encode('utf-8')
key = int(key, 2).to_bytes(len(key) // 8, 'big')
enc = b''
for i in range(len(baph)):
enc += (baph[i] ^ key[i % len(key)]).to_bytes(1, 'big')
return enc
enc = encrypt(flag)
f = open('flag.enc', 'wb')
f.write(enc)
f.close()
☆方針・解法
既知平文攻撃的な考え方で解けます。
暗号文の長さは 48 バイトで、また Key の作り方から Key の長さは 6 バイトであることが分かります。フラグの始まりは「CCTF」なので1、Base64 エンコードした結果の先頭 6バイトは必ず「Q0NURn」になります。Key と xor する直前のデータの先頭は「q0nurN」ですから、これと暗号文の先頭 6 バイトを xor すれば Key は求まります。この Key を使って暗号化と逆の手順を踏めば復号できます。
☆ソルバ
from base64 import b64decode
from pwn import xor
enc = open('flag.enc', 'rb').read()
_k = b"q0nurN"
key = xor(_k, enc[0:6])
_dec = b''
for i in range(len(enc)):
_dec += (enc[i] ^ key[i % len(key)]).to_bytes(1, 'big')
dec = ""
for b in _dec.decode('utf-8'):
if b.islower():
dec += b.upper()
else:
dec += b.lower()
dec = b64decode(dec.encode('utf-8'))
print(dec)
☆フラグ
CCTF{UpP3r_0R_lOwER_17Z_tH3_Pr0bL3M}
2-2. Klamkin (難易度:easy, 61 points, 83 solves)
☆設問
サーバに接続して解く問題です。
q, r, s が指定され、(a * r + b * s) % q == 0 となる a, b に対し (a * x + b * y) % q == 0 となる x, yを求めます。但し x, y いずれかのビット数が指定されます。
☆方針・解法
q, r, s が与えられた段階で、拡張ユークリッド互除法で条件を満た a, b を計算します。x や y の条件(ビット数、以下 "bits" と記す)が示されたら、サーバに以下の x, y を送ります。
・ x にビット数指定があった場合: x = 2**(bits-1)+1, y = s * x * a % q
・ y にビット数指定があった場合: x = -r * y * b % q, y = 2**(bits-1)+1
5 問正答するとフラグを得られました。
☆フラグ
CCTF{f1nDin9_In7Eg3R_50Lut1Ons_iZ_in73rEStIn9!}
2-3. polyRSA (難易度:medium-easy, 42 points, 145 solves)
☆設問
以下のスクリプト等が与えられます。
#!/usr/bin/env python3
from Crypto.Util.number import *
from flag import flag
def keygen(nbit = 64):
while True:
k = getRandomNBitInteger(nbit)
p = k**6 + 7*k**4 - 40*k**3 + 12*k**2 - 114*k + 31377
q = k**5 - 8*k**4 + 19*k**3 - 313*k**2 - 14*k + 14011
if isPrime(p) and isPrime(q):
return p, q
def encrypt(msg, n, e = 31337):
m = bytes_to_long(msg)
return pow(m, e, n)
p, q = keygen()
n = p * q
enc = encrypt(flag, n)
print(f'n = {n}')
print(f'enc = {enc}')
n = 44538727182858207226040251762322467288176239968967952269350336889655421753182750730773886813281253762528207970314694060562016861614492626112150259048393048617529867598499261392152098087985858905944606287003243
enc = 37578889436345667053409195986387874079577521081198523844555524501835825138236698001996990844798291201187483119265306641889824719989940722147655181198458261772053545832559971159703922610578530282146835945192532
☆方針・解法
SageMath の力を借りて解くことにしました。
競技中に行った解法は、「p, q を k を変数とする多項式と見たとき、p * q の定数項 を n から引いた数は k を割りるので、n - [p * q の定数項] の約数から k の候補を探索する」というものでした。
しかし、素直に f = p * q - n と置き、f の factor を見ればすぐ解けました。反省。
☆ソルバ(反省後)
from Crypto.Util.number import *
n = 44538727182858207226040251762322467288176239968967952269350336889655421753182750730773886813281253762528207970314694060562016861614492626112150259048393048617529867598499261392152098087985858905944606287003243
enc = 37578889436345667053409195986387874079577521081198523844555524501835825138236698001996990844798291201187483119265306641889824719989940722147655181198458261772053545832559971159703922610578530282146835945192532
e = 31337
PR.<k> = PolynomialRing(ZZ)
_p = k**6 + 7 * k**4 - 40 * k**3 + 12 * k**2 - 114 * k + 31377
_q = k**5 - 8 * k**4 + 19 * k**3 - 313 * k**2 - 14 * k + 14011
f = _p * _q - n
for x in f.factor():
if x[0].degree() == 1:
_k = -x[0].coefficients()[0]
p = _p(k=_k)
q = _q(k=_k)
phi = (p-1)*(q-1)
d = inverse(e, int(phi))
_pt = pow(int(enc), int(d), int(n))
m = long_to_bytes(pow(int(enc), int(d), int(n)))
if b"CCTF{" in m:
print(m)
break
☆フラグ
CCTF{F4C70r!N9_tRIcK5_aR3_fUN_iN_RSA?!!!}
2-4. SOTS (難易度:medium-easy, 49 points, 113 solves)
☆設問
サーバに接続して解く問題です。
n が示され、 x^2 + y^2 == n を満たす x と y を求める問題です。
☆方針・解法
Gauss 数体(の整数環)での素因数分解を考えれば OK です。
yafu を使ってn を素因数分解し、各素因数に対して SageMath で Gauss 数体での分解を計算し、その結果をもとに送信する x, y を構築しました。
競技時に示された n は整数の範囲で 3 つの素因数に分解されたため、
$$(x_1^2 + y_1^2) (x_2^2 + y_2^2) (x_3^2 + y_3^2) = X^2 + Y^2$$
ただし
$$(X,Y) := (x_1x_2x_3 + x_3y_1y_2 - x_2y_1y_3 + x_1y_2y_3, x_2x_3y_1 - x_1x_3y_2 + x_1x_2y_3 + y_1y_2y_3)$$
を利用して解を求めました。
☆ソルバ(n を素因数分解した後の計算に使ったもの)
n1 = 1403575103598950078497501
n2 = 1832215985571864173006269
n3 = 824314708499254102530821
R = ZZ[I]
n1 = R(n1)
x1 = factor(n1)[1][0][0]
y1 = factor(n1)[1][0][1]
n2 = R(n2)
x2 = factor(n2)[1][0][0]
y2 = factor(n2)[1][0][1]
n3 = R(n3)
x3 = factor(n3)[1][0][0]
y3 = factor(n3)[1][0][1]
X = abs(x1*x2*x3 + x3*y1*y2 - x2*y1*y3 + x1*y2*y3)
Y = abs(x2*x3*y1 - x1*x3*y2 + x1*x2*y3 + y1*y2*y3)
print(n1*n2*n3)
print((x1^2+y1^2)*(x2^2+y2^2)*(x3^2+y3^2))
print(X+Y)
print(f"{X},{Y}")
☆フラグ
CCTF{3Xpr3sS_4z_Th3_sUm_oF_7w0_Squ4rE5!}
2-5. Volgo (難易度:medium-easy, 65 points, 77 solves)
☆設問
Web サイトが示され、そこでは M-209 2 で任意(アルファベット大文字のみ)の平文を暗号化するサービスと、同じ暗号化方式・キーで暗号化したフラグが提供されます。
☆方針・解法
この暗号装置では 5 文字 1 ブロックで扱われ、またいろいろ試してみると先頭 2 ブロックと末尾 2 ブロックは Padding であることが推測されます。
また、鍵はわかりませんが、試してみると各文字の暗号化は独立している(他の暗号文に影響されない)ことが推測されます。
以上のことから、平文に相当する文字数(155文字)分、各アルファベット大文字を暗号化し。その結果から平文を求めることができます。
☆ソルバ
import requests
alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
url1 = "http://<省略>/flag/fetch"
res = requests.get(url1)
enc = eval(res.text)['flag'].replace(' ', '')[10:-10]
d = {}
url2 = "http://<省略>/m209/encipher"
for i in range(0x41, 0x41+26):
to_send = chr(i)*155
res = requests.post(url2,data={"plain":to_send})
d[chr(i)] = eval(res.text)['cipher'].replace(' ', '')[10:-10]
flag = ""
for i in range(len(enc)):
for c in alpha:
print(enc[i], d[c][i], c)
if enc[i] == d[c][i]:
flag += c
break
print(flag.replace("Z"," "))
#YOU KNOW HOW TO FORMAT FLAG JUST PUT UPCOMING LETTERS WITHIN CURLY BRACES FOLLOWED BY CCTF OOJMPMDDIXCLNNWFTEJUMFXKBRVVMOPSLSSLUTXVDVNDMYYPHPWFJRNJBVBOMUYR
☆フラグ
CCTF{OOJMPMDDIXCLNNWFTEJUMFXKBRVVMOPSLSSLUTXVDVNDMYYPHPWFJRNJBVBOMUYR}
2-6. Jeksign (難易度:medium-easy, 100 points, 45 solves)
☆設問
サーバに接続して解く問題です。
本問ではスクリプトが示されています3。
1337(z^4 - x^2) == 31337(y^2 - z^4) を満たす x, y, z を答える問題です。設問は 19 個あり、z のビット数は 30-bit から 1 ずつ増え 48-bit まで指定されます。
#!/usr/bin/env python3
from Crypto.Util.number import *
from secret import gensol, nbit_gensol
from flag import flag
m = bytes_to_long(flag.encode('utf-8'))
print(m)
a = 1337
b = 31337
def die(*args):
pr(*args)
quit()
def pr(*args):
s = " ".join(map(str, args))
sys.stdout.write(s + "\n")
sys.stdout.flush()
def sc():
return sys.stdin.readline().strip()
def main():
border = "|"
pr(border*72)
pr(border, "Welcome crypto guys! Here we are looking for the solution of special", border)
pr(border, "Diophantine equation: 1337(z^4 - x^2) = 31337(y^2 - z^4) in natural ", border)
pr(border, "numbers, in each stage solve the equation in mentioned interval :) ", border)
pr(border*72)
STEP, level = 0, 19
while True:
p, q = nbit_gensol(1337, 31337, STEP + 30)
x, y, z = gensol(a, b, p, q)
pr(border, f"Send a solution like `x, y, z' such that the `z' is {STEP + 30}-bit: ")
ans = sc()
try:
X, Y, Z = [int(_) for _ in ans.split(',')]
NBIT = Z.bit_length()
except:
die(border, 'Your input is not valid, Bye!')
if 1337*(Z**4 - X**2) == 31337*(Y**2 - Z**4) and NBIT == STEP + 30:
if STEP == level - 1:
die(border, f'Congrats, you got the flag: {flag}')
else:
pr('| good job, try to solve the next challenge :P')
STEP += 1
else:
die(border, 'your answer is not correct, Bye!!')
if __name__ == '__main__':
main()
☆方針・解法
z を 1 つ決めてしまえば、x = y = z^2 という自明な解が塞がれていないので簡単に通ってしまいます。
☆ソルバ
import telnetlib
HOST = <省略>
PORT = <省略>
def readline():
return tn.read_until(b"\n")
def sendline(to_send):
tn.write(to_send.encode() + b"\n")
tn = telnetlib.Telnet(HOST, PORT)
for i in range(5):
readline()
for i in range(30, 49):
print(readline())
z = 2**(i-1)
x = z**2
y = z**2
to_send = str(x) + "," + str(y) + "," + str(z)
sendline(to_send)
print(readline())
☆フラグ
CCTF{4_diOpH4nT1nE_3Qua7i0n__8Y__Jekuthiel_Ginsbur!!}
2-7. Keydream (難易度:medium-easy, 105 points, 42 solves)
☆設問
RSA 問です。以下のスクリプト等が提示されます。
#!/usr/bin/env python3
from Crypto.Util.number import *
import string
from flag import flag
def randstr(l):
rstr = [(string.printable[:62] + '_')[getRandomRange(0, 62)] for _ in range(l)]
return ''.join(rstr)
def encrypt(msg, l):
while True:
rstr = 'CCTF{it_is_fake_flag_' + randstr(l) + '_90OD_luCk___!!}'
p = bytes_to_long(rstr.encode('utf-8'))
q = bytes_to_long(rstr[::-1].encode('utf-8'))
if isPrime(p) and isPrime(q):
n = p * q
e, m = 65537, bytes_to_long(msg.encode('utf-8'))
c = pow(m, e, n)
return n, c
n, c = encrypt(flag, 27)
print(f'n = {n}')
print(f'c = {c}')
n = 23087202318856030774680571525957068827041569782431397956837104908189620961469336659300387982516148407611623358654041246574100274275974799587138270853364165853708786079644741407579091918180874935364024818882648063256767259283714592098555858095373381673229188828791636142379379969143042636324982275996627729079
c = 3621516728616736303019716820373078604485184090642291670706733720518953475684497936351864366709813094154736213978864841551795776449242009307288704109630747654430068522939150168228783644831299534766861590666590062361030323441362406214182358585821009335369275098938212859113101297279381840308568293108965668609
☆方針・解法
pの(qもですが)十分小さい一部分だけが未知ですので、Coppersmith(SageMath の small_roots)で解けます。
☆ソルバ
from Crypto.Util.number import *
n = 23087202318856030774680571525957068827041569782431397956837104908189620961469336659300387982516148407611623358654041246574100274275974799587138270853364165853708786079644741407579091918180874935364024818882648063256767259283714592098555858095373381673229188828791636142379379969143042636324982275996627729079
c = 3621516728616736303019716820373078604485184090642291670706733720518953475684497936351864366709813094154736213978864841551795776449242009307288704109630747654430068522939150168228783644831299534766861590666590062361030323441362406214182358585821009335369275098938212859113101297279381840308568293108965668609
e = 65537
l = 27
PR.<x> = PolynomialRing(Zmod(n))
base = bytes_to_long(b'CCTF{it_is_fake_flag_' + b'\x00' * l + b'_90OD_luCk___!!}')
f = 256**16 * x + base
f = f.monic()
r = f.small_roots(X=256**l, beta=0.3)
p = int(256**16 * r[0] + base)
q = n // p
assert p * q == n
phi = (p-1)*(q-1)//GCD(p-1,q-1)
d = inverse(e, phi)
print(long_to_bytes(pow(int(c),int(d),int(n))))
☆フラグ
CCTF{h0M3_m4dE_k3Y_Dr1vEn_CrYp7O_5ySTeM!}
2-8. Infinity castle (難易度:medium-easy, 131 points, 32 solves)
☆設問
以下のスクリプト等が提示されます。
RSA 問で、p, q の計算に必要な情報が示されますが、何も考えずそのまま計算式を実行するとベラボーな時間がかかる仕掛けとなっています。
#!/usr/bin/env python3
from Crypto.Util.number import *
from os import urandom
class TaylorSeries():
def __init__(self, order):
self.order = order
def coefficient(self, n):
i = 0
coef = 1
while i < n:
i += 1
coef *= (coef * (1/2-i+1)) / i
return coef
def find(self, center):
sum = 0
center -= 1
i = 0
while i < self.order:
sum += (center**(1/2-i) * self.coefficient(i))
i += 1
return sum
def xor(cip, key):
repeation = 1 + (len(cip) // len(key))
key = key * repeation
key = key[:len(cip)]
msg = bytes([c ^ k for c, k in zip(cip, key)])
return msg
# If you run these 3 functions (diamond, triage, summarize) with big numbers, they will never end
# You need to optimize them first
def diamond(num):
output = 0
for i in range(num):
output += i*2 + 1
return output
def triage(num):
output = 0
index = 0
for i in range(num):
output += (i+index)*6 + 1
index += i
return output
def summarize(b):
order = getRandomRange(1000, 2000)
t = TaylorSeries(order)
output = 0
i = 2
while i < b:
b1 = t.find(i)
b2 = t.find(i+1)
output += (b1+b2) / 2
i += 1
return output
KEY_SIZE = 2048
p, q = [getPrime(KEY_SIZE) for _ in '01']
e, n = 0x10001, p*q
f = open ('./message.txt', 'rb')
message = f.read()
f.close()
msg_length = len(message)
padded_msg = message + urandom(len(long_to_bytes(n)) - msg_length)
padded_msg_length = len(padded_msg)
xor_key = long_to_bytes(int(summarize(n)))[:padded_msg_length]
assert(len(xor_key) == 512)
enc0 = xor(padded_msg, xor_key)
assert(bytes_to_long(enc0) < n)
c0 = abs(diamond(p) - diamond(q))
c1 = triage(p)
c1 += 3*n*(p+q)
c1 += triage(q)
m = bytes_to_long(enc0)
enc1 = pow(m, e, n)
print(f"c0 = {c0}")
print(f"c1 = {c1}")
print(f"enc = {enc1}")
c0 = 194487778980461745865921475300460852453586088781074246328543689440168930873549359227127363759885970436167330043371627413542337857082400024590112412164095470165662281502211335756288082198009158469871465280749846525326701136380764079685636158337203211609233704905784093776008350120607841177268965738426317288541638374141671346404729428158104872411498098187946543666987926130124245185069569476433120128275581891425551325847219504501925282012199947834686225770246801353666030075146469275695499044424390475398423700504158154044180028733800154044746648133536737830623670383925046006108348861714435567006327619892239876863209887013290251890513192375749866675256952802329688897844132157098061758362137357387787072005860610663777569670198971946904157425377235056152628515775755249828721767845990597859165193162537676147173896835503209596955703313430433124688537275895468076469102220355973904702901083642275544954262724054693167603475188412046722656788470695566949847884250306735314144182029335732280420629131990311054229665691517003924788583771265625694414774865289407885678238795596912006567817508035434074250123869676396153982762032750080222602796273083162627489526255501643913672882350236497429678928099868244687228703074267962827621792960
c1 = 102325064080381160170299055162846304227217463467232681115953623386548494169967745781550171804781503102663445039561476870208178139548389771866145006535051362059443515034504958703659546162037904784821960707630188600064568878674788077706711506213779443920430038560373854184526850365974450688458342413544179732143225845085164110594063440979455274250021370090572731855665413325275414458572412561502408983107820534723804225765540316307539962199024757378469001612921489902325166003841336027940632451584642359132723894801946906069322200784708303615779699081247051006259449466759863245508473429631466831174013498995506423210088549372249221415401309493511477847517923201080509933268996867995533386571564898914042844521373220497356837599443280354679778455765441375957556266205953496871475611269965949025704864246188576674107448587696941054123618319505271195780178776338475090463487960063464172195337956577785477587755181298859587398321270677790915227557908728226236404532915215698698185501562405374498053670694387354757252731874312411228777004316623425843477845333936913444143768519959591070492639650368662529749434618783626718975406802741753688956961837855306815380844030665696781685152837849982159679122660714556706669865596780528277684800454866433826417980212164051043504955615794706595412705883261111953152250227679858538249797999044336210905975316421254442221408945203647754303635775048438188044803368249944201671941949138202928389951227347433255867504906597772044398973241425387514239164853656233776024571606159378910745571588836981735827902305330330946219857271646498602227088657739442867033212012876837654750348578740516798444734534291467314881324902354425889448102902750077077381896216130734894767553834949561471219923459897244690006643798812989271282475503126962274052192342840870308710338336817452628324667507548265612892611100350882163205858101959976
enc = 122235247669762131006183252122503937958296387791525458429403709404875223067116491817728568224832483391622109986550732469556761300197133827976956875865159629512476600711420561872409721582387803219651736262581445978042694384374119142613277808898398213602093802571586386354257378956087240174787723503606671543195193114158641301908622673736098768829071270132073818245595918660134745516367731595853832524328488074054536278197115937409643221809577554866060292157239061557708159310445977052686561229611117673473208278176118561352693319461471419694590218295911647368543698198762827636021268989705079848502749837879584394379300566277359149621932579222865430374652678738198451655509408564586496375811666876030847654260305392984710580761255795785508407844683687773983669843744274915862335181251050775093896006210092665809300090715190088851654138383362259256212093670748527819287681468901379286722214112321906917311154811516336259463356911326393701445497605365038857575515541024824906818473933597129846235905072394148879079996812146836910199111439031562946495046766063326815863624262541346543552652673629442370320109404700346028639853707278295255350982238521659924641921142615894039995513480511108116053798143154593343124822462519555715118822045
☆方針・解法
「膨大な計算量を数学の力で何とかする」的な問題です。個人的な好き嫌いで言えば、こういうのが「大好き」です。
コードを読み解くと、diamond は平方、triage は立方を計算するものであることが分かります(高校数学のΣ計算を思い出しましょう)。
よって、c0 = |p^2-q^2|、c1 = (p+q)^3 であることがわかり、これらから容易に p, q が計算できます。
また、TaylorSeries の find は平方根の近似計算にほかなりません。summarize は平方根の総和をベースに算出できますが、平方根の総和には近似式
$$\sum_{k=1}^n \sqrt{k} \fallingdotseq \frac{2n^{\frac{3}{2}}}{3} + \frac{\sqrt{n}}{2} $$
があるのでそれを利用します4。
☆ソルバ
from Crypto.Util.number import *
from ptrlib import xor
import gmpy2
def summarize(n):
K = RealField(4096)
x = K(n)
sum = 2 * x^(3/2) /3 + x^(1/2)/2
return sum - 1 - K(2).nth_root(2)/2 - x.nth_root(2)/2
c0 = 194487778980461745865921475300460852453586088781074246328543689440168930873549359227127363759885970436167330043371627413542337857082400024590112412164095470165662281502211335756288082198009158469871465280749846525326701136380764079685636158337203211609233704905784093776008350120607841177268965738426317288541638374141671346404729428158104872411498098187946543666987926130124245185069569476433120128275581891425551325847219504501925282012199947834686225770246801353666030075146469275695499044424390475398423700504158154044180028733800154044746648133536737830623670383925046006108348861714435567006327619892239876863209887013290251890513192375749866675256952802329688897844132157098061758362137357387787072005860610663777569670198971946904157425377235056152628515775755249828721767845990597859165193162537676147173896835503209596955703313430433124688537275895468076469102220355973904702901083642275544954262724054693167603475188412046722656788470695566949847884250306735314144182029335732280420629131990311054229665691517003924788583771265625694414774865289407885678238795596912006567817508035434074250123869676396153982762032750080222602796273083162627489526255501643913672882350236497429678928099868244687228703074267962827621792960
c1 = 102325064080381160170299055162846304227217463467232681115953623386548494169967745781550171804781503102663445039561476870208178139548389771866145006535051362059443515034504958703659546162037904784821960707630188600064568878674788077706711506213779443920430038560373854184526850365974450688458342413544179732143225845085164110594063440979455274250021370090572731855665413325275414458572412561502408983107820534723804225765540316307539962199024757378469001612921489902325166003841336027940632451584642359132723894801946906069322200784708303615779699081247051006259449466759863245508473429631466831174013498995506423210088549372249221415401309493511477847517923201080509933268996867995533386571564898914042844521373220497356837599443280354679778455765441375957556266205953496871475611269965949025704864246188576674107448587696941054123618319505271195780178776338475090463487960063464172195337956577785477587755181298859587398321270677790915227557908728226236404532915215698698185501562405374498053670694387354757252731874312411228777004316623425843477845333936913444143768519959591070492639650368662529749434618783626718975406802741753688956961837855306815380844030665696781685152837849982159679122660714556706669865596780528277684800454866433826417980212164051043504955615794706595412705883261111953152250227679858538249797999044336210905975316421254442221408945203647754303635775048438188044803368249944201671941949138202928389951227347433255867504906597772044398973241425387514239164853656233776024571606159378910745571588836981735827902305330330946219857271646498602227088657739442867033212012876837654750348578740516798444734534291467314881324902354425889448102902750077077381896216130734894767553834949561471219923459897244690006643798812989271282475503126962274052192342840870308710338336817452628324667507548265612892611100350882163205858101959976
enc = 122235247669762131006183252122503937958296387791525458429403709404875223067116491817728568224832483391622109986550732469556761300197133827976956875865159629512476600711420561872409721582387803219651736262581445978042694384374119142613277808898398213602093802571586386354257378956087240174787723503606671543195193114158641301908622673736098768829071270132073818245595918660134745516367731595853832524328488074054536278197115937409643221809577554866060292157239061557708159310445977052686561229611117673473208278176118561352693319461471419694590218295911647368543698198762827636021268989705079848502749837879584394379300566277359149621932579222865430374652678738198451655509408564586496375811666876030847654260305392984710580761255795785508407844683687773983669843744274915862335181251050775093896006210092665809300090715190088851654138383362259256212093670748527819287681468901379286722214112321906917311154811516336259463356911326393701445497605365038857575515541024824906818473933597129846235905072394148879079996812146836910199111439031562946495046766063326815863624262541346543552652673629442370320109404700346028639853707278295255350982238521659924641921142615894039995513480511108116053798143154593343124822462519555715118822045
p_plus_q = gmpy2.iroot(c1,3)[0]
p_minus_q = c0 // p_plus_q
p = int((p_plus_q + p_minus_q) // 2)
q = int(p_plus_q - p)
n = p * q
phi = (p-1)*(q-1)//GCD(p-1,q-1)
d = inverse(0x10001, phi)
m = pow(enc, d, n)
xor_key = long_to_bytes(int(summarize(n)))[:512]
msg = xor(long_to_bytes(int(m)), xor_key)
print(msg)
☆フラグ
CCTF{Mix1ng_c4lcUluS_w17h_Numb3r_The0Rry_S33ms_s7r0ng_cRyp70_Sch3m4!!}
2-9. Aniely (難易度:medium, 66 points, 76 solves)
☆設問
以下のスクリプト等が提示されます。
chacha20 の亜流のような stream 暗号です。
#!/usr/bin/env python3
from struct import *
from os import *
from secret import passphrase, flag
def aniely_stream(passphrase):
def mixer(u, v):
return ((u << v) & 0xffffffff) | u >> (32 - v)
def forge(w, a, b, c, d):
for i in range(2):
w[a] = (w[a] + w[b]) & 0xffffffff
w[d] = mixer(w[a] ^ w[d], 16 // (i + 1))
w[c] = (w[c] + w[d]) & 0xffffffff
w[b] = mixer(w[b] ^ w[c], (12 + 2*i) // (i + 1))
bring = [0] * 16
bring[:4] = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]
bring[4:12] = unpack('<8L', passphrase)
bring[12] = bring[13] = 0x0
bring[14:] = [0] * 2
while True:
w = list(bring)
for _ in range(10):
forge(w, 0x0, 0x4, 0x8, 0xc)
forge(w, 0x1, 0x5, 0x9, 0xd)
forge(w, 0x2, 0x6, 0xa, 0xe)
forge(w, 0x3, 0x7, 0xb, 0xf)
forge(w, 0x0, 0x5, 0xa, 0xf)
forge(w, 0x1, 0x6, 0xb, 0xc)
forge(w, 0x2, 0x7, 0x8, 0xd)
forge(w, 0x3, 0x4, 0x9, 0xe)
for c in pack('<16L', *((w[_] + bring[_]) & 0xffffffff for _ in range(16))):
yield c
bring[12] = (bring[12] + 1) & 0xffffffff
if bring[12] == 0:
bring[13] = (bring[13] + 1) & 0xffffffff
def aniely_encrypt(msg, passphrase):
if len(passphrase) < 32:
passphrase = (passphrase * (32 // len(passphrase) + 1))[:32]
rand = urandom(2) * 16
return bytes(a ^ b ^ c for a, b, c in zip(msg, aniely_stream(passphrase), rand))
key = bytes(a ^ b for a, b in zip(passphrase, flag))
enc = aniely_encrypt(passphrase, key)
print(f'key = {key.hex()}')
print(f'enc = {enc.hex()}')
key = 4dcceb8802ae3c45fe80ccb364c8de19f2d39aa8ebbfb0621623e67aba8ed5bc
enc = e67a67efee3a80b66af0c33260f96b38e4142cd5d9426f6f156839f2e2a8efe8
☆方針・解法
「rand を固定したとき aniely_encrypt(enc, key) = passphrase」をエスパーしたので解けました。
rand の部分は 2 バイトなので総当たりします。
☆ソルバ
from Crypto.Util.number import *
from struct import *
from os import *
from pwn import xor
key = bytes.fromhex("4dcceb8802ae3c45fe80ccb364c8de19f2d39aa8ebbfb0621623e67aba8ed5bc")
enc = bytes.fromhex("e67a67efee3a80b66af0c33260f96b38e4142cd5d9426f6f156839f2e2a8efe8")
def aniely_stream(passphrase):
def mixer(u, v):
return ((u << v) & 0xffffffff) | u >> (32 - v)
def forge(w, a, b, c, d):
for i in range(2):
w[a] = (w[a] + w[b]) & 0xffffffff
w[d] = mixer(w[a] ^ w[d], 16 // (i + 1))
w[c] = (w[c] + w[d]) & 0xffffffff
w[b] = mixer(w[b] ^ w[c], (12 + 2*i) // (i + 1))
bring = [0] * 16
bring[:4] = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]
bring[4:12] = unpack('<8L', passphrase)
bring[12] = bring[13] = 0x0
bring[14:] = [0] * 2
while True:
w = list(bring)
for _ in range(10):
forge(w, 0x0, 0x4, 0x8, 0xc)
forge(w, 0x1, 0x5, 0x9, 0xd)
forge(w, 0x2, 0x6, 0xa, 0xe)
forge(w, 0x3, 0x7, 0xb, 0xf)
forge(w, 0x0, 0x5, 0xa, 0xf)
forge(w, 0x1, 0x6, 0xb, 0xc)
forge(w, 0x2, 0x7, 0x8, 0xd)
forge(w, 0x3, 0x4, 0x9, 0xe)
for c in pack('<16L', *((w[_] + bring[_]) & 0xffffffff for _ in range(16))):
yield c
bring[12] = (bring[12] + 1) & 0xffffffff
if bring[12] == 0:
bring[13] = (bring[13] + 1) & 0xffffffff
def aniely_encrypt(msg, passphrase):
if len(passphrase) < 32:
passphrase = (passphrase * (32 // len(passphrase) + 1))[:32]
#rand = urandom(2) * 16
rand = b"\x00"*32
return bytes(a ^ b ^ c for a, b, c in zip(msg, aniely_stream(passphrase), rand))
_passphrase = aniely_encrypt(enc, key)
for i in range(0x10000):
passphrase = xor(_passphrase, i.to_bytes(2,"big"))
flag = xor(passphrase, key)
if b"CCTF{" in flag:
print(flag)
break
☆フラグ
CCTF{7rY_t0_D3cRyPT_z3_ChaCha20}
2-10. Diploma (難易度:medium, 72 points, 68 solves)
☆設問
行列の order を求める問題が複数出されます。
☆方針・解法
Jordan 標準形を求め、対角成分(固有多項式の根)の order の LCM を計算します5。
order の計算では他に乗ずるべき値があるのですが、結果的に 1 になるものしか出題されなかったので無視できました。
(2022/7/26追記:Jordan 標準形の計算を手で行わず、直接 M.multiplicative_order() で行列の order は計算できました。SageMath 強いです。)
☆ソルバ(汚いです。スミマセン。)
from Crypto.Util.number import *
import telnetlib
HOST = <省略>
PORT = <省略>
def readline():
return tn.read_until(b"\n")
def readuntil(stopper):
return tn.read_until(stopper.encode())
def sendline(to_send):
tn.write(to_send.encode() + b"\n")
def linestrip(s):
s = s.strip()
while(True):
_s = s
s = s.replace(" ", " ")
if _s == s:
break
s = s.replace("[ ", "[")
s = s.replace(" ]", "]")
s = s.replace(" ", ",")
return eval(s)
tn = telnetlib.Telnet(HOST, PORT)
for i in range(6):
readline()
while(True):
p = int(readline().decode().replace("| Generating the parameters for p = ", "").replace(", please wait...", ""))
print("p=", p)
readline()
data = readuntil("| Send the order of matrix M:").decode().split("\n")
#print(data)
m = []
for i in range(len(data)-1):
m.append(linestrip(data[i]))
print(m)
M = Matrix(GF(p),m)
s = len(M.rows())
chM = M.charpoly()
K.<a> = chM.splitting_field()
_M = Matrix(K, M)
J, X = _M.jordan_form(transformation=True)
ans = 1
for i in range(s):
ans = LCM(ans, J[i][i].multiplicative_order())
print(ans)
sendline(str(ans))
readline()
result = readline()
print(result)
if b"CCTF" in result:
break
☆フラグ
CCTF{ma7RicES_4R3_u5EfuL_1n_PUbl!c-k3y_CrYpt0gr4Phy!}
2-11. Cantilever (難易度:medium, 79 points, 60 solves)
☆設問
以下の通り、スクリプト等が提示されます。
#!/usr/bin/env python3
from Crypto.Util.number import *
from flag import flag
def gen_primes(nbit, imbalance):
p = 2
FACTORS = [p]
while p.bit_length() < nbit - 2 * imbalance:
factor = getPrime(imbalance)
FACTORS.append(factor)
p *= factor
rbit = (nbit - p.bit_length()) // 2
while True:
r, s = [getPrime(rbit) for _ in '01']
_p = p * r * s
if _p.bit_length() < nbit: rbit += 1
if _p.bit_length() > nbit: rbit -= 1
if isPrime(_p + 1):
FACTORS.extend((r, s))
p = _p + 1
break
FACTORS.sort()
return (p, FACTORS)
def genkey(nbit, imbalance, e):
while True:
p, FACTORS = gen_primes(nbit // 2, imbalance)
if len(FACTORS) != len(set(FACTORS)):
continue
q, q_factors = gen_primes(nbit // 2, imbalance + 1)
if len(q_factors) != len(set(q_factors)):
continue
factors = FACTORS + q_factors
if e not in factors:
break
n = p * q
return n, (p, q)
nbit = 2048
imbalance = 19
e = 0x10001
m_1 = bytes_to_long(flag[:len(flag) // 2])
m_2 = bytes_to_long(flag[len(flag) // 2:])
n, PRIMES = genkey(nbit, imbalance, e)
c_1 = pow(m_1, e, n)
c_2 = pow(e, m_2, n)
print(f'n = {n}')
print(f'c_1 = {c_1}')
print(f'c_2 = {c_2}')
n = 7069789930583271525053215046247773438899869283661158227309691853515987055334306019600324056376312479212090202373516405860759222837585952590589336295698718699890424169542280710721069784487366121478569760563045886361884895363592898476736269784284754788133722060718026577238640218755539268465317292713320841554802703379684173485217045274942603346947299152498798736808975912326592689302969859834957202716983626393365387411319175917999258829839695189774082810459527737342402920881184864625678296442001837072332161966439361793009893108796934406114288057583563496587655548536011677451960307597573257032154009427010069578913
c_1 = 488692928085934899944055554857568564903346089951134051486941368561567330884363274156339625953702601270565654444836193796061118053575538224794730472032345171432952984560662218697488844007827176184413713651118743456250147472678673801728916283759932987216388378211555067885210167894310696549664382751443669387953644382833924884208966436685137553434532738845959014828804809425096115758364136546390809453200055265653531950423111482644330073443545410319576097902472017235065047191133112557289289189187696092145850680765843608118584107829268136014912479701945735063525799796920293418182776436767911172221104640501952880057
c_2 = 109770827223661560471527567179288748906402603483328748683689436879660543465776899146036833470531024202351087008847594392666852763100570391337823820240726499421306887565697452868723849092658743267256316770223643723095601213088336064635680075206929620159782416078143076506249031972043819429093074684182845530529249907297736582589125917235222921623698038868900282049587768700860009877737045693722732170123306528145661683416808514556360429554775212088169626620488741903267154641722293484797745665402402381445609873333905772582972140944493849645600529147490903067975300304532955461710562911203871840101407995813072692212
☆方針・解法
n の素因数は 2^19-smooth なので、p, q の算出には p-1 法が使えます。
c_1 は通常の RSA で復号できますが、 c_2 は discrete log の計算が必要でしたので SageMath の力を借りました。
☆ソルバ
#https://haru-note3.com/prime/Pollards-p-minus-1-algorithm.html
from sympy import primerange, nextprime
import math
def p_minus_1_method(n, B, a=2):
for prime in primerange(1, B + 1):
l = math.floor(math.log(n) / math.log(prime))
a = pow(a, prime**l, n)
#print(prime)
d = math.gcd(a - 1, n)
if 1 < d and d < n:
return d
else:
return "unsolved"
n = 7069789930583271525053215046247773438899869283661158227309691853515987055334306019600324056376312479212090202373516405860759222837585952590589336295698718699890424169542280710721069784487366121478569760563045886361884895363592898476736269784284754788133722060718026577238640218755539268465317292713320841554802703379684173485217045274942603346947299152498798736808975912326592689302969859834957202716983626393365387411319175917999258829839695189774082810459527737342402920881184864625678296442001837072332161966439361793009893108796934406114288057583563496587655548536011677451960307597573257032154009427010069578913
c_1 = 488692928085934899944055554857568564903346089951134051486941368561567330884363274156339625953702601270565654444836193796061118053575538224794730472032345171432952984560662218697488844007827176184413713651118743456250147472678673801728916283759932987216388378211555067885210167894310696549664382751443669387953644382833924884208966436685137553434532738845959014828804809425096115758364136546390809453200055265653531950423111482644330073443545410319576097902472017235065047191133112557289289189187696092145850680765843608118584107829268136014912479701945735063525799796920293418182776436767911172221104640501952880057
c_2 = 109770827223661560471527567179288748906402603483328748683689436879660543465776899146036833470531024202351087008847594392666852763100570391337823820240726499421306887565697452868723849092658743267256316770223643723095601213088336064635680075206929620159782416078143076506249031972043819429093074684182845530529249907297736582589125917235222921623698038868900282049587768700860009877737045693722732170123306528145661683416808514556360429554775212088169626620488741903267154641722293484797745665402402381445609873333905772582972140944493849645600529147490903067975300304532955461710562911203871840101407995813072692212
B = 2**19
a = 2
while(True):
res = p_minus_1_method(n, B, a)
if res != "unsolved":
print(res)
break
a = nextprime(a)
print(a)
#83408372012221120677052349409462320990177094246143674474872152829440524098582262384066400107950985845255268335597502228206679771838750219696329523257176739436871327238322817403970284015587320158034304282786944710043150568360761457471641695390427267786485448748458445872307883254297662715749746270343116946519
from Crypto.Util.number import *
n = 7069789930583271525053215046247773438899869283661158227309691853515987055334306019600324056376312479212090202373516405860759222837585952590589336295698718699890424169542280710721069784487366121478569760563045886361884895363592898476736269784284754788133722060718026577238640218755539268465317292713320841554802703379684173485217045274942603346947299152498798736808975912326592689302969859834957202716983626393365387411319175917999258829839695189774082810459527737342402920881184864625678296442001837072332161966439361793009893108796934406114288057583563496587655548536011677451960307597573257032154009427010069578913
c_1 = 488692928085934899944055554857568564903346089951134051486941368561567330884363274156339625953702601270565654444836193796061118053575538224794730472032345171432952984560662218697488844007827176184413713651118743456250147472678673801728916283759932987216388378211555067885210167894310696549664382751443669387953644382833924884208966436685137553434532738845959014828804809425096115758364136546390809453200055265653531950423111482644330073443545410319576097902472017235065047191133112557289289189187696092145850680765843608118584107829268136014912479701945735063525799796920293418182776436767911172221104640501952880057
c_2 = 109770827223661560471527567179288748906402603483328748683689436879660543465776899146036833470531024202351087008847594392666852763100570391337823820240726499421306887565697452868723849092658743267256316770223643723095601213088336064635680075206929620159782416078143076506249031972043819429093074684182845530529249907297736582589125917235222921623698038868900282049587768700860009877737045693722732170123306528145661683416808514556360429554775212088169626620488741903267154641722293484797745665402402381445609873333905772582972140944493849645600529147490903067975300304532955461710562911203871840101407995813072692212
e = 0x10001
p = 83408372012221120677052349409462320990177094246143674474872152829440524098582262384066400107950985845255268335597502228206679771838750219696329523257176739436871327238322817403970284015587320158034304282786944710043150568360761457471641695390427267786485448748458445872307883254297662715749746270343116946519
q = n // p
assert p*q == n
phi = (p-1)*(q-1)//GCD(p-1,q-1)
d1 = int(inverse(e, phi))
m1 = int(pow(int(c_1), d1,n))
Fp = GF(p)
Fq = GF(q)
c2p = Fp(c_2)
c2q = Fq(c_2)
ep = Fp(e)
eq = Fq(e)
dp = discrete_log(c2p, ep)
dq = discrete_log(c2q, eq)
m2 = int(crt([dp,dq],[p-1,q-1]))
m = long_to_bytes(m1) + long_to_bytes(m2)
print(long_to_bytes(m1))
☆フラグ
CCTF{5L3Ek_4s__s1lK__Ri9H7?!}
2-12. DBB (難易度:medium, 103 points, 43 solves)
☆設問
ECDLP 問です。以下の通りスクリプト等が提示されます。
#!/usr/bin/env sage
from Crypto.Util.number import *
from secret import n, B, BASE_POINT, FLAG
m = bytes_to_long(FLAG)
assert m < n
F = IntegerModRing(n)
E = EllipticCurve(F, [31337, B])
BASE_POINT = E(BASE_POINT)
P = m * BASE_POINT
print(f'n = {n}')
print(f'BASE_POINT.x = {BASE_POINT.xy()[0]}')
print(f'P = {P.xy()[0], P.xy()[1]}')
n = 34251514713797768233812437040287772542697202020425182292607025836827373815449
BASE_POINT.x = 7331
P = (10680461779722115247262931380341483368049926186118123639977587326958923276962, 4003189979292111789806553325182843073711756529590890801151565205419771496727)
☆方針・解法
n が合成数なので、n の各素因数を法とした楕円曲線での DLP を解き、その結果を CRT して元の楕円曲線での DLP の解を導きます(Pohlig-Hellman法)。
CRT するときの modulo は各楕円曲線における G の order となることに注意します。
☆ソルバ
n = 34251514713797768233812437040287772542697202020425182292607025836827373815449
bx = 7331
P = (10680461779722115247262931380341483368049926186118123639977587326958923276962, 4003189979292111789806553325182843073711756529590890801151565205419771496727)
B = P[1]^2 - P[0]^3 - 31337 * P[0] % n
F = IntegerModRing(n)
E = EllipticCurve(F, [31337, B])
G = E.lift_x(bx)
P = E(P)
ps = [11522256336953175349, 14624100800238964261, 203269901862625480538481088870282608241]
assert n == ps[0]*ps[1]*ps[2]
E0 = EllipticCurve(GF(ps[0]), [31337, B])
P0 = E0(P)
G0 = E0(G)
d0 = G0.discrete_log(P0)
E1 = EllipticCurve(GF(ps[1]), [31337, B])
P1 = E1(P)
G1 = E1(G)
d1 = G1.discrete_log(P1)
E2 = EllipticCurve(GF(ps[2]), [31337, B])
P2 = E2(P)
G2 = E2(G)
d2 = G2.discrete_log(P2)
m = crt([int(d0),int(d1),int(d2)],[G0.order(),G1.order(),G2.order()])
print(long_to_bytes(m))
☆フラグ
CCTF{p0Hl!9_H31LmaN_4tTackin9!}
2-13. Starter ECC (難易度:medium, 103 points, 43 solves)
☆設問
ECDLP 問・・・と見せかけて、実は合成数における平方根算出問題です。
#!/usr/bin/env sage
from Crypto.Util.number import *
from secret import n, a, b, x, flag
y = bytes_to_long(flag.encode('utf-8'))
assert y < n
E = EllipticCurve(Zmod(n), [a, b])
try:
G = E(x, y)
print(f'x = {x}')
print(f'a = {a}')
print(f'b = {b}')
print(f'n = {n}')
print('Find the flag :P')
except:
print('Ooops, ERROR :-(')
x = 10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046477020617917601884853827611232355455223966039590143622792803800879186033924150173912925208583
a = 31337
b = 66826418568487077181425396984743905464189470072466833884636947306507380342362386488703702812673327367379386970252278963682939080502468506452884260534949120967338532068983307061363686987539408216644249718950365322078643067666802845720939111758309026343239779555536517718292754561631504560989926785152983649035
n = 117224988229627436482659673624324558461989737163733991529810987781450160688540001366778824245275287757373389887319739241684244545745583212512813949172078079042775825145312900017512660931667853567060810331541927568102860039898116182248597291899498790518105909390331098630690977858767670061026931938152924839936
Find the flag :P
☆方針・解法
n をいくつかに分解し、それぞれの mod. で平方根を計算して CRT をとります。
平方根は各々の mod. で複数存在し得るので、フラグ(b"CCTF{")を含むものを全探索します。
各 mod. での平方根計算では、sqrt だとなぜか失敗することが多いので、nth_root を使ったところサクッと計算できました。
☆ソルバ(競技で使ったものを一部改良)
from Crypto.Util.number import *
x = 10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046477020617917601884853827611232355455223966039590143622792803800879186033924150173912925208583
a = 31337
b = 66826418568487077181425396984743905464189470072466833884636947306507380342362386488703702812673327367379386970252278963682939080502468506452884260534949120967338532068983307061363686987539408216644249718950365322078643067666802845720939111758309026343239779555536517718292754561631504560989926785152983649035
n = 117224988229627436482659673624324558461989737163733991529810987781450160688540001366778824245275287757373389887319739241684244545745583212512813949172078079042775825145312900017512660931667853567060810331541927568102860039898116182248597291899498790518105909390331098630690977858767670061026931938152924839936
n0 = 2^63
n1 = 690712633549859897233^6
n2 = 651132262883189171676209466993073^5
assert n == n0*n1*n2
R0 = Zmod(n0)
x0 = R0(x)
y0s = R0(x0^3 + a*x0 + b).nth_root(2, all=True)
R1 = Zmod(n1)
x1 = R1(x)
y1s = R1(x1^3 + a*x1 + b).nth_root(2, all=True)
R2 = Zmod(n2)
x2 = R2(x)
y2s = R2(x2^3 + a*x2 + b).nth_root(2, all=True)
for y0 in y0s:
for y1 in y1s:
for y2 in y2s:
y = crt([int(y0),int(y1),int(y2)],[int(n0),int(n1),int(n2)])
m = long_to_bytes(y)
if b"CCTF{" in m:
print(m)
break
☆フラグ
CCTF{8E4uTy_0f_L1f7iN9_cOm3_Up!!}
2-14. Oak land (難易度:medium, 122 points, 35 solves)
☆設問
DLP っぽい問題です。以下のスクリプト等が提示されます。
#!/usr/bin/env python3
from Crypto.Util.number import *
from flag import flag
p = 7389313481223384214994762619823300589978423075857540712007981373887018860174846208000957230283669342186460652521580595183523706412588695116906905718440770776239313669678685198683933547601793742596023475603667
e = 31337
f = 7236042467316654159796543399639966340258093274047941788600980451877044636122969830708918356119442228154447395855689559447196348683125675305629837437591088260218138895919514078948650757100432223219969122629790
g = 1878626136321051642174045874618248475160620873585704351202865003185878331837410979441756843820270907300810543618813757245154196050399357659526631164136221434463496532263979506870318259276669412698827040743576
x = bytes_to_long(flag.encode('utf-8'))
assert x < p
c = (110 * pow(e, x, p) + 313 * pow(f, x, p) + 114 * pow(g, x, p)) % p
print(f'c = {c}')
c = 871346503375040565701864845493751233877009611275883500035764036792906970084258238763963152627486758242101207127598485219754255161617890137664012548226251138485059295263306930653899766537171223837761341914356
☆方針・解法
最初見たときは無理ゲーじゃね?と思ったのですが、f や g を e のべき乗で表現して pow(e, x, p) を X と置けば、 c は X の多項式で表せるので、X が計算できさらに DLP で x が分かりそうです。
☆ソルバ(実際は SageMath のコンソールに 1 行ずつ入力して計算を進めました)
from Crypto.Util.number import *
c = 871346503375040565701864845493751233877009611275883500035764036792906970084258238763963152627486758242101207127598485219754255161617890137664012548226251138485059295263306930653899766537171223837761341914356
p = 7389313481223384214994762619823300589978423075857540712007981373887018860174846208000957230283669342186460652521580595183523706412588695116906905718440770776239313669678685198683933547601793742596023475603667
e = 31337
f = 7236042467316654159796543399639966340258093274047941788600980451877044636122969830708918356119442228154447395855689559447196348683125675305629837437591088260218138895919514078948650757100432223219969122629790
g = 1878626136321051642174045874618248475160620873585704351202865003185878331837410979441756843820270907300810543618813757245154196050399357659526631164136221434463496532263979506870318259276669412698827040743576
F = GF(p)
g = F(g)
f = F(f)
e = F(e)
c = F(c)
a = discrete_log(f, e) #p-2
b = discrete_log(g, e) #p-3
PR.<X> = PolynomialRing(F)
pol = (110*X^3 + 313*X + 114 - c * X^2 ).monic()
xx = pol.roots()[0][0] #6621736714803975486469770941631918297557515256645141403866696899656801529069492623812629032566382224631133756513615225604518504150834965984951182186972792703483282014259169249309903503054330793332575590535531
x = discrete_log(xx,e)
#130669746807581038734209381694055163726297497321549047017439654852953086676029610991997
print(long_to_bytes(x))
☆フラグ
CCTF{V33333rY_eeeeZy_DLP_cH41L3n9E!}
2-15. Resign (難易度:medium, 122 points, 35 solves)
☆設問
サーバに接続して解く署名系の問題です。
以下の通り、スクリプトが提示されます。
#!/usr/bin/env python3
from Crypto.Util.number import *
from hashlib import sha1
import sys
from flag import flag
def die(*args):
pr(*args)
quit()
def pr(*args):
s = " ".join(map(str, args))
sys.stdout.write(s + "\n")
sys.stdout.flush()
def sc():
return sys.stdin.readline().strip()
def main():
border = "|"
pr(border*72)
pr(border, " Hi, Try to guess our RSA private key to sign my message, talented ", border)
pr(border, " hackers like you ;) are able to do it, they are *Super Guesser* :) ", border)
pr(border*72)
p, q = [getPrime(1024) for _ in '__']
n, e = p * q, 65537
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
MSG = b'::. Can you forge any signature? .::'
h = bytes_to_long(sha1(MSG).digest())
SIGN = pow(h, d, n)
while True:
pr("| Options: \n|\t[G]uess the secret key \n|\t[R]eveal the parameters \n|\t[S]ign the message \n|\t[P]rint the signature \n|\t[Q]uit")
ans = sc().lower()
if ans == 'g':
pr(border, f"please send the RSA public exponent and PARAMS p, q separated by comma like e, p, q: ")
PARAMS = sc()
try:
E, P, Q = [int(_) for _ in PARAMS.split(',')]
if P.bit_length() == Q.bit_length() == 1024 and P != Q:
N = P * Q
PHI = (P - 1) * (Q - 1)
D = inverse(E, PHI)
if pow(h, D, N) == SIGN:
e, n, d = E, N, D
pr(border, 'Great guess, now you are able to sign any message!!!')
else:
pr(border, 'Your RSA parameters are not correct!!')
else: raise Exception('Invalid RSA parameters!!')
except: pr(border, "Something went wrong!!")
elif ans == 'r':
pr(border, f'e = {e}')
pr(border, f'n = {n}')
elif ans == "p":
pr(border, f'SIGN = {SIGN}')
elif ans == 's':
pr(border, "Please send the signature of this message: ")
pr(border, f"MSG = {MSG[4:-4]}")
sgn = sc()
try:
sgn = int(sgn)
_continue = True
except:
pr(border, "Something went wrong!!")
_continue = False
if _continue:
_MSG = MSG[4:-4]
_h = bytes_to_long(sha1(_MSG).digest())
if pow(sgn, e, n) == _h:
die(border, "Congrats! your got the flag: " + flag)
else:
pr(border, "Sorry, your signature is not correct!")
elif ans == 'q': die("Quitting ...")
else: die("Bye bye ...")
if __name__ == "__main__": main()
☆方針・解法
競技終了が近かったので、思考回路がgdgdで以下のようにしか考えられませんでした。
やるべきこと:pow(sgn, e, n) == _h となる sgn を答える
できること:e, n(,d) を別の E. N (,D) にすげ替える(ただし、D = inverse(E,phi(N)としたとき、pow(h, D, N) == SIGNを満たさなければならない)
どうすればよいか: sign = pow(_h, D, N) を送ればよい。ただし、D, N は pow(h, D, N) == SIGN を満たさなければいけない。
⇒ smooth な P, Q で N を構成し、D = discrete_log(SIGH, h) を探索します。mod.P(mod.Q)でこの DLP が解をもつとは限らないので、解を持つようなもので、なおかつ E = inverse(D, phi(N))が計算できるようなものを選定しなければならない。
⇒ smooth な P, Q で、D に関する DLP が解けて E が計算できるようなものをガチャ
・・・とこんな具合に考えて作ったのが以下のソルバです。
☆ソルバ(競技でつかったものほぼそのままですので、超汚いです)
from Crypto.Util.number import *
import telnetlib
from hashlib import sha1
from random import randint
import sys
HOST = <省略>
PORT = <省略>
def readline():
return tn.read_until(b"\n")
def readuntil(stopper):
return tn.read_until(stopper.encode())
def sendline(to_send):
tn.write(to_send.encode() + b"\n")
S = 20 #20はテキトー
def getSmoothPrime():
base = 1
for i in range(1024 // S):
base *= getPrime(S)
#L = 1024 - int(base).bit_length()
L = 1024 - base.nbits()
print(L)
while(True):
#x = int(base * randint(2**(L-1),2**L) + 1)
x = 2*getPrime(L-1)*base + 1
#print(x.nbits())
#if isPrime(x) and x.bit_length() == 1024:
if isPrime(x) and x.nbits() == 1024:
return x
smoothprimes = []
for i in range(30):#30はテキトー
smoothprimes.append(getSmoothPrime())
print("start:")
tn = telnetlib.Telnet(HOST, PORT)
for i in range(10):
readline()
MSG = b'::. Can you forge any signature? .::'
h = bytes_to_long(sha1(MSG).digest())
sendline("P")
SIGN = int(readline().decode().replace("| SIGN = ",""))
print(f"SIGN={SIGN}")
cands=[]
ok = False
i = 0
while(True):
try:
p = smoothprimes[i]
i += 1
Fp = GF(p)
hp = Fp(h)
SIGNp = Fp(SIGN)
dp = discrete_log(SIGNp, hp)
cand = (int(p), int(dp))
print(cand)
if not cand in cands:
for _cand in cands:
try:
P = cand[0]
Q = _cand[0]
D = int(crt([cand[1],_cand[1]],[P-1, Q-1]))
PHI = (P-1)*(Q-1)
E = inverse(D, PHI)
N = P * Q
if (E*D - 1) % PHI != 0:
print("bad E and D")
print(GCD(E, PHI), GCD(D, PHI))
elif pow(int(h), D, int(N)) != SIGN % N:
print("bad D")
else:
ok = True
break
except Exception as ex:
print(ex)
pass
cands.append(cand)
except Exception as ex:
print(ex)
continue
if ok:
break
if i >= 30: #30はテキトー
exit()
print(f"P={P}")
print(f"Q={Q}")
print(f"E={E}")
print(f"D={D}")
print(f"N={N}")
for i in range(6):
readline()
#print(readline())
sendline("G")
print(readline())
to_send = str(E) + ", " + str(P) + ", " + str(Q)
sendline(to_send)
print(readline())
for i in range(6):
readline()
sendline("S")
print(readline())
print(readline())
_MSG = MSG[4:-4]
_h = bytes_to_long(sha1(_MSG).digest())
sgn = pow(_h, D, N)
to_send = str(sgn)
sendline(to_send)
print(readline())
print(readline())
☆フラグ
CCTF{Unkn0wN_K3y_5h4rE_4t7aCk_0n_Th3_RSA!}
※おそらくもっとスマートに解けるはずです。。。
2-16. Sparse (難易度:medium-hard, 114 points, 38 solves)
☆設問
RSA 問です。以下の通りスクリプト等が提示されます。
#!/usr/bin/env python3
from Crypto.Util.number import *
from flag import flag
def sparse(p, k):
nbit = p.bit_length()
while True:
CF = [getRandomRange(-1, 1) for _ in '_' * k]
XP = [getRandomRange(3, nbit - 3) for _ in '_' * k]
A = sum([CF[_] * 2 ** XP[_] for _ in range(0, k)])
q = p + A
if isPrime(q) * A != 0:
return q
p = getPrime(417)
q = sparse(p, 5)
e, n = 65537, p * q
print(f'n = {n}')
m = bytes_to_long(flag.encode('utf-8'))
assert m < n
c = pow(m, e, n)
print(f'c = {c}')
n = 94144887513744538681657844856583985690903055376400570170371837200724227314957348031684706936655253125445176582486308241015430205703156336248578475428712275706238423997982248462635972817633320331030484841129628650918661036694615254018290264619628335177
c = 80250313885079761377138486357617323555591919111371649902793873860183455237161293320577683249054725852540874552433031133240624696119120378419135912301004715004977978507247634217071922495893934816945961054193052791946557226599493364850793396744903765857
☆方針・解法
p, q の構成法に問題があり、p に対し、q = p - d, d = (1~5個の2冪の和)という形で作っています。
つまり、d を全探索し、X^2 - X*d - n = 0 が素数の解を持った時それが p の候補となります。
実際、d は 2**284 でしたが、もし d が 5 個の 2 ベキの和だと計算時間的に苦しく、う~んと思った問題でした。
☆ソルバ(例によって汚いです)
import itertools
import gmpy2
from Crypto.Util.number import *
n = 94144887513744538681657844856583985690903055376400570170371837200724227314957348031684706936655253125445176582486308241015430205703156336248578475428712275706238423997982248462635972817633320331030484841129628650918661036694615254018290264619628335177
c = 80250313885079761377138486357617323555591919111371649902793873860183455237161293320577683249054725852540874552433031133240624696119120378419135912301004715004977978507247634217071922495893934816945961054193052791946557226599493364850793396744903765857
e = 65537
def check(d):
if gmpy2.iroot(d**2 + 4*n,2)[1] != True:
return False
D = gmpy2.iroot(d**2 + 4*n,2)[0]
if GCD((d + D) // 2, n) != 1:
p = (d + D) // 2
q = n // p
print(p, q)
phi = (p-1)*(q-1)//GCD(p,q)
d = inverse(e, phi)
print(long_to_bytes(pow(c,d,n)))
return True
else:
return False
table = []
for i in range(3, 414):
table.append(2**i)
for i in [1,5]:
print(i)
for X in itertools.combinations(table, i):
d = 0
for x in X:
d += x
if check(d):
exit()
☆フラグ
CCTF{5pArs3_dIfFeRenc3_f4ct0r1za7iOn_m3th0d!}
2-17. Lagima (難易度:medium-hard, 164 points, xx solves)
☆設問
以下のスクリプト等が提示されます。
対称群における DLP のような問題です。
#!/usr/bin/env sage
from flag import flag
def genperm(n):
_ = list(range(1, n + 1))
shuffle(_)
return _
def genlatrow(n):
A = []
for _ in range(n): A.append(genperm(n))
return A
def prodlat(A, B):
assert len(A) == len(B)
C, G = [], SymmetricGroup(len(A))
for _ in range(len(A)):
g = (G(A[_]) * G(B[_])).tuple()
C.append(list(g))
return C
def powlat(A, n):
assert n >= 0
B = bin(n)[2:]
c, R = len(B), [list(range(1, len(A) + 1)) for _ in range(len(A))]
if n == 0: return R
else:
for b in B:
if b == '1':
if c == 1: R = prodlat(R, A)
else:
T = A
for _ in range(c - 1): T = prodlat(T, T)
R = prodlat(R, T)
c -= 1
return R
G = genlatrow(313)
secret = int.from_bytes(flag.lstrip(b'CCTF{').rstrip(b'}'), 'big')
H = powlat(G, secret)
print(f'G = {G}')
print(f'H = {H}')
G = <313×313の配列、省略>
H = <313×313の配列、省略>
☆方針・解法
数学的なウラをとらず、カンだけで解きました(競技なのでヨシ!)
LCM(1,2,...,313)を考え、各素因数の冪の形で表します。Pohlig-Hellman法のような形で DLP を解きますが、order の倍数が出てきてしまいました。
そこで、各 mod での解のうち 1 は怪しいので除外!のような形で CRT し直したところフラグゲットできました(極めてイイカゲンですけど競技なのでヨシ!)。
問題の本質は違いますが、個人的には「Larisaのツラい版」という印象でした。
☆ソルバ(御多分に漏れず汚いです)
#!/usr/bin/env sage
from Crypto.Util.number import *
import copy
G = <313×313の配列、省略>
H = <313×313の配列、省略>
def genperm(n):
_ = list(range(1, n + 1))
shuffle(_)
return _
def genlatrow(n):
A = []
for _ in range(n): A.append(genperm(n))
return A
def prodlat(A, B):
assert len(A) == len(B)
C, G = [], SymmetricGroup(len(A))
for _ in range(len(A)):
g = (G(A[_]) * G(B[_])).tuple()
C.append(list(g))
return C
def powlat(A, n):
assert n >= 0
B = bin(n)[2:]
c, R = len(B), [list(range(1, len(A) + 1)) for _ in range(len(A))]
if n == 0: return R
else:
for b in B:
if b == '1':
if c == 1: R = prodlat(R, A)
else:
T = A
for _ in range(c - 1): T = prodlat(T, T)
R = prodlat(R, T)
c -= 1
return R
L = 313
od = 1
primes = []
for i in range(2, L+1):
if isPrime(i):
j = 1
while(True):
if i**(j+1) > L:
break
j+=1
primes.append(i**j)
od *= (i**j)
print(primes)
def powl2(A, facs, p):
AA = copy.copy(A)
for f in facs:
if f != p:
AA = powlat(AA, f)
return AA
def dlog(B, X, p):
BB = copy.copy(B)
for i in range(1, p+1):
if BB == X:
return i
BB = prodlat(BB, B)
return None
ms = []
ds = []
for i in range(len(primes)):
p = primes[i]
GG = powl2(G, primes, p)
assert powlat(GG, p+1) == GG
HH = powl2(H, primes, p)
assert powlat(HH, p+1) == HH
d = dlog(GG, HH, p)
print(f"p:{p}")
print(f"d:{d}")
ms.append(p)
ds.append(d)
print(ms)
print(ds)
_ms = []
_ds = []
for i in range(len(ms)):
if ds[i] != 1:
_ms.append(ms[i])
_ds.append(ds[i])
d = crt(_ds, _ms)
print(d)
print(long_to_bytes(d))
☆フラグ
CCTF{3lGam4L_eNcR!p710n_4nD_L4T!n_5QuarS3!}
2-18. Larisa (難易度:medium-hard, 174 points, 22 solves)
☆設問
以下のスクリプト等が提示されます。
「Lagima」と同じく対称群が出てきますが若干毛色が違う問題です。
#!/usr/bin/env sage
from flag import flag
def genperm(n):
_ = list(range(1, n + 1))
shuffle(_)
return _
def genlatrow(n):
A = []
for _ in range(n): A.append(genperm(n))
return A
def prodlat(A, B):
assert len(A) == len(B)
C, G = [], SymmetricGroup(len(A))
for _ in range(len(A)):
g = (G(A[_]) * G(B[_])).tuple()
C.append(list(g))
return C
def powlat(A, n):
assert n >= 0
B = bin(n)[2:]
c, R = len(B), [list(range(1, len(A) + 1)) for _ in range(len(A))]
if n == 0: return R
else:
for b in B:
if b == '1':
if c == 1: R = prodlat(R, A)
else:
T = A
for _ in range(c - 1): T = prodlat(T, T)
R = prodlat(R, T)
c -= 1
return R
def pad(msg, n):
assert len(msg) <= n
return msg + msg[-1] * (n - len(msg))
def embed(msg, n):
assert len(msg) < n
msg = pad(msg, n)
while True:
r, s = [randint(2, n) for _ in '__']
if gcd(r, len(msg)) == 1:
break
A = []
for _ in range(n):
while True:
R = genperm(n)
if R[(_ * r + s) % n] == ord(msg[_]):
A.append(R)
break
return A
def encrypt(A, e = 65537):
return powlat(A, e)
l, e = 128, 65537
M = embed(flag, l)
C = encrypt(M, e)
print(C)
<127×127の配列、省略>
☆方針・解法
powlat(A, x) == powlat(A, 0) となる x (>0) を考えたとき、e*d = 1 mod x となる e, d について、
powlat(powlat(A, e),d) == A となることに注意します。
このことを用いて C を「decrypt」し、embed の逆操作をすればフラグをゲットできます。
☆ソルバ
from Crypto.Util.number import *
C = <127×127の配列、省略>
def prodlat(A, B):
assert len(A) == len(B)
C, G = [], SymmetricGroup(len(A))
for _ in range(len(A)):
g = (G(A[_]) * G(B[_])).tuple()
C.append(list(g))
return C
def powlat(A, n):
assert n >= 0
B = bin(n)[2:]
c, R = len(B), [list(range(1, len(A) + 1)) for _ in range(len(A))]
if n == 0: return R
else:
for b in B:
if b == '1':
if c == 1: R = prodlat(R, A)
else:
T = A
for _ in range(c - 1): T = prodlat(T, T)
R = prodlat(R, T)
c -= 1
return R
def readflag(A, n):
for r in range(2, n):
for s in range(2, n):
flag = b""
for i in range(n):
flag += (A[i][(i * r + s) % n]).to_bytes(1 , "big")
if b"CCTF" in flag:
return flag
return None
def encrypt(A, e = 65537):
return powlat(A, e)
l, e = 128, 65537
od = 1
for i in range(2, l+1):
if isPrime(i):
j = 1
while(True):
if i**(j+1) > l:
break
j+=1
od *= (i ** j)
d = inverse(e, od)
P = encrypt(C, d)
assert encrypt(P,e) == C
print(readflag(P, l))
☆フラグ
CCTF{pUbliC_k3y_crypt0graphY_u5in9_rOw-l4t!N_5quAr3S!}
3. おわりに
途中細かい休憩や食事は挟みましたが、ほぼ 24 時間稼働していたので疲れました。
この週末の気力体力を全振りしてしまったため、謎解き CTF はそもそも登録しそびれ、Imaginary CTF はほぼ手つかずとなってしまいました。
来年も元気ならばソロ参加してさらに上(順位的にというよりは、solveの数と質の向上)を目指したいと思います。
ジーク、ジオン!