SECCON CTF 2021の参加記録
はじめに
VLANouroboros の HK-ilohas として,SECCON CTF 2021 に参加しました.
僕が解けたのは pppp と oOoOoO の 2 問だけですが,記念に参加記録でも書いておくことにしました.実力不足と未だに脱初心者できねー!って感じで非常に悔しいです.
pppp
問題ファイル
from Crypto.Util.number import *
from flag import flag
p = getStrongPrime(512)
q = getStrongPrime(512)
n = p*q
mid = len(flag) // 2
e = 65537
m1 = int.from_bytes(flag[:mid], byteorder='big')
m2 = int.from_bytes(flag[mid:], byteorder='big')
assert m1 < 2**256
assert m2 < 2**256
m = [[p,p,p,p], [0,m1,m1,m1], [0,0,m2,m2],[0,0,0,1]]
# add padding
for i in range(4):
for j in range(4):
m[i][j] *= getPrime(768)
m = matrix(Zmod(p*q), m)
c = m^e
print("n =", n)
print("e =", e)
print("c =", list(c))
考察 & 解法
これを
とりあえず,手計算で色々と試してみると,対角成分がちょうど
このことから,p = gcd(c[0][0], n)
を計算することで
本番中はここから先で詰まってしまいました.
そこで,落ち着いて条件を見直してみると,
from math import gcd
from Crypto.Util.number import *
n = 139167515668183984855584233262421636549219808362436809125322963984953234794207403032462532211718407628015534917936237180092470832870352873174416729863982860547330562153111496168661222608038945799305565324740297535609102402946273092600303759078983973524662838350143815732516927299895302494977521033451618509313
e = 65537
c = [(92641859227150025014514674882433433169736939888930400782213731523244191029744271714915087397818608658221982921496921528927873080896272971564627162670330785041427348269531449548757383647994986600796703130771466176972483905051546758332111818555173685323233367295631863710855125823503925281765070200264928761744, 1077078501560459546238096407664459657660011596619515007448272718633593622581663318232822694070053575817000584000976732545349394411037957356817674297166036371321332907845398174111343765006738074197964396832305908342965034091516961317164203682771449331094865994143953470394418754170915147984703343671839620070, 19878161032897109459692857500488708331148676837923170075630073845924376353394086221031683671854185288619608305138965881628353471119235227157715699650190844508727073649527735233175347600167954253143204293274253676829607434380971492999430389536409563073620686264607716424139208756197843637115228155976163983619, 122958657434560838063916316490126514822437273152981380647634868499620566657448363565613345650206126542999322277498960954804580159527199119604554047697342524367459283765958189416627623253226055220105627822118413649499651442079969872322463271891353808314530249098525814619479135297014148780695960117897387220659), (0, 85635304452753185796593135650704585992713419302092444931829191186284566226617686976975731459756968679710078670232999566062343743901469759277582454092882685887985731708244015567469990157564460035983017331880588783841581502687752495254387549274422591338211161917565559735193456411356422539814020979699927207024,
26528377397409932803048052918715873209845190225305139460936852681030879561522825277119360099719008486268731610926098705442795761739644784858085976938906030639986454157616558457541083641717564142619063815917161350343604401278251069255966146207538326575595944701499010180658631016268689550402326369924649514049, 17173480018007185616783556851363148729840100207266610547324632027095687866456613104465211034834604995290825437734467654701021261504226847008483339028335703977866796341754911432666568936460974103742649586111260163432789617417125379644939110280618415377202845096157056174169392363954229816964869557167190373166), (0, 0, 81417110160690915414859599923077760437964436481940074249510026432592954854440295980578313776441414052192070135409849396229653279814546498083873720679422968334818254076803899882280264290639872486915551889441082468560654475422089052988909565455596584407805229280743723696618903551087160338683566908533474596220, 88524270641123978066493517684012199807956329430551155649688209766850898125045959831704501988313531767120589113923546449704920649814085765896894870692227804052901254644766662594723181025793077392532746071480212649880063471693730914835259139038459097504431147211622052068997412540488201406879310193174863792764), (0, 0, 0, 130146806238985078905344376697263038970354607413027156915068014483770022716717215156189413217688976902906182579031431264733207976605553885314360422441780388319618199732296392330859801016851191010568169307878720202422104375360360029207688301496478751250969744747470242179561459045172707909287093959859681318497)]
p = gcd(c[0][0], n)
q = n // p
d = pow(e, -1, (p-1)*(q-1))
m1r11 = pow(c[1][1], d, n)
m2r22 = pow(c[2][2], d, n)
r11 = 890941089701337639742923670629764992383197102649368033477055542426171609788212371754143394788213291456300216361551640449365841636339404366563906765128981689755176699342918904671528454362377482743831820969956624190146702130809843851
m1 = m1r11 // r11
r22 = 1001860113482890555017710485492420423876242421736095074738385131002929692458471110177277127310688011729127988908466398932019505500863392332136664225101034793814078136979376956873726395552928651794389376576934283525340824695529351511
m2 = m2r22 // r22
flag = long_to_bytes(m1) + long_to_bytes(m2)
print(flag.decode())
$ python3 solve.py
SECCON{C4n_y0u_prove_why_decryptable?}
oOoOoO
問題ファイル
import signal
from typing import MutableSequence
from Crypto.Util.number import long_to_bytes, bytes_to_long, getPrime
import random
from flag import flag
message = b""
for _ in range(128):
message += b"o" if random.getrandbits(1) == 1 else b"O"
M = getPrime(len(message) * 5)
S = bytes_to_long(message) % M
print("M =", M)
print('S =', S)
print('MESSAGE =', message.upper().decode("utf-8"))
print(f"{message=}")
signal.alarm(600)
ans = input('message =').strip().encode()
if ans == message:
print(flag)
else:
print("🧙")
考察 & 本番中の解法
問題をぼんやりと眺めていると,これは o と O の差分 0x20 を各桁に足す足さないのナップザック暗号の類だと気が付きました.
これを基に格子を作ってみました.
from Crypto.Util.number import long_to_bytes, bytes_to_long
MESSAGE = b"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO"
M = 4296072306836887687980673774381704166205403018972787671575165621123442878815781831549000956483723234218910010200125467663329060281140685621573979820006074362442678060712929561126912283226883523
S = 1965402809320098074246818343271877025564965958587689534033304168965677237622715285275273898016472591781012471103692096198637466125821215781239405507711963471167044804932449051695673349602793955
diff_char = 0x20
B = matrix.column(ZZ, vector([diff_char * 16 ^ (2*i) for i in range(128)]))
B = B.augment(matrix.identity(128))
B = B.stack(vector([S - bytes_to_long(MESSAGE)] + [0] * 128))
B = B.stack(vector([M] + [0] * 128))
B = B.LLL()
v = B[1][1:]
print(v)
message = b""
for k in v:
if k != 0:
message = b"o" + message
else:
message = b"O" + message
print(message)
# SECCON{Here_is_Huge-Huge_Island_yahhoOoOoOoOoOoO}
ちゃんと解き直したバージョン
上の方法ではたまにしか解けませんでした.それを修正したのが次のスクリプトです.
from pwn import *
from Crypto.Util.number import bytes_to_long
MESSAGE = b"O" * 128
message = b""
diff_char = 0x20 # b"o" - b"O"
io = remote("oooooo.quals.seccon.jp", 8000)
io.recvuntil(b"M = ")
M = int(io.recvline())
io.recvuntil(b"S = ")
S = int(io.recvline())
# create lattice
B = matrix(ZZ, 130, 130)
for i in range(128):
B[i, 0] = diff_char * 16 ^ (2*i)
for i in range(129):
B[i, i+1] = 1
B[128, 0] = bytes_to_long(MESSAGE) - S
B[129, 0] = M
B = B.dense_matrix()
B = B.BKZ()
v = B[0]
for k in v[1:-1]:
if abs(k) == 1:
message = b"o" + message
else:
message = b"O" + message
print(message)
io.recvuntil(b"message =")
io.sendline(message)
print(io.recvline().decode())
解けなかった問題の反省
XXX は格子は組めたのですが,LLL 後の取り出し周りで失敗してました.卒論とかが落ち着いたら,格子をちゃんと勉強しなおそうと思いました(永遠にやらないパターン).
cerberus は残り 1 時間で解法に気が付いたのですが,外でラーメン食べている最中だったのでコーディングが間に合わなかったです 😢 今度は早めに AES 問題に取り掛かります…
CCC は何にもわからなかったです.
Sign_Wars は何かこれも格子っぽいなーって思いつつも,うまく作れませんでした.どのみち,後半の乱数パートもわからなかったので詰みですが.
来年こそ…!!
Discussion