UTCTF 2024
RSA-256
N is only 256 bits so it can be factored directly.
$ sage
┌────────────────────────────────────────────────────────────────────┐
│ SageMath version 10.2, Release Date: 2023-12-03 │
│ Using Python 3.11.8. Type "help()" for help. │
└────────────────────────────────────────────────────────────────────┘
sage: N = 77483692467084448965814418730866278616923517800664484047176015901835675610073
sage: N.nbits()
256
sage: %time factor(N)
CPU times: user 4min 57s, sys: 1.29 s, total: 4min 59s
Wall time: 5min 2s
1025252665848145091840062845209085931 * 75575216771551332467177108987001026743883
N = 77483692467084448965814418730866278616923517800664484047176015901835675610073
e = 65537
c = 43711206624343807006656378470987868686365943634542525258065694164173101323321
p = 1025252665848145091840062845209085931
q = 75575216771551332467177108987001026743883
phi = (p-1)*(q-1)
d = pow(e, -1, phi)
flag = pow(c, d, N)
print(bytes.fromhex(f"{flag:02x}").decode())
# utflag{just_send_plaintext}
Beginner: Anti-dcode.fr
from caesarcipher import CaesarCipher
out = open('LoooongCaesarCipher.txt').read()
shifted = CaesarCipher(out, offset=8).decoded
flag = shifted.split('utflag')[1][:11]
print('utflag' + flag)
# utflag{rip_dcode}
numbers go brrr
The seed can be bruteforced:
from Crypto.Cipher import AES
from pwn import remote
from tqdm import trange
def get_random_number():
global seed
seed = int(str(seed * seed).zfill(12)[3:9])
return seed
def decrypt(ciphertext):
key = b''
for _ in range(8):
key += (get_random_number() % (2 ** 16)).to_bytes(2, 'big')
cipher = AES.new(key, AES.MODE_ECB)
return cipher.decrypt(ciphertext)
io = remote("betta.utctf.live", 7356)
io.recv()
io.sendline(b"1")
ciphertext = bytes.fromhex(io.recv().decode().split()[-1])
for seed in trange(10**6):
flag = decrypt(ciphertext)
if b"utflag" in flag:
print(flag)
print()
break
# utflag{deep_seated_and_recurring_self-doubts}
numbers go brrr 2
The 3 seeds can again be bruteforced:
from Crypto.Util.Padding import pad
from Crypto.Cipher import AES
from pwn import remote
from tqdm import trange
def get_random_number():
global seed
seed = int(str(seed * seed).zfill(12)[3:9])
return seed
def encrypt(message):
key = b''
for _ in range(8):
key += (get_random_number() % (2 ** 16)).to_bytes(2, 'big')
cipher = AES.new(key, AES.MODE_ECB)
ciphertext = cipher.encrypt(pad(message, AES.block_size))
return key.hex(), ciphertext.hex()
io = remote("betta.utctf.live", 2435)
for i in range(3):
io.recv()
io.sendline(b"2")
io.recv()
if i == 0:
io.recv()
io.sendline(b"a")
enc = io.recvline().decode().split()[-1]
for seed in trange(10**6):
encrypt(b"random text to initalize key")
key, ciphertext = encrypt(b"a")
if ciphertext == enc:
io.recv()
io.sendline(b"1")
io.recv()
io.sendline(key.encode())
io.recv().decode()
break
print(io.recv())
# utflag{ok_you_are_either_really_lucky_or_you_solved_it_as_intended_yay}
bits and pieces
n1 can be factored with Fermat’s method, then n2 and n3 have a common factor.
from math import isqrt, gcd
n1 = 16895844090302140592659203092326754397916615877156418083775983326567262857434286784352755691231372524046947817027609871339779052340298851455825343914565349651333283551138205456284824077873043013595313773956794816682958706482754685120090750397747015038669047713101397337825418638859770626618854997324831793483659910322937454178396049671348919161991562332828398316094938835561259917841140366936226953293604869404280861112141284704018480497443189808649594222983536682286615023646284397886256209485789545675225329069539408667982428192470430204799653602931007107335558965120815430420898506688511671241705574335613090682013
c1 = 7818321254750334008379589501292325137682074322887683915464861106561934924365660251934320703022566522347141167914364318838415147127470950035180892461318743733126352087505518644388733527228841614726465965063829798897019439281915857574681062185664885100301873341937972872093168047018772766147350521571412432577721606426701002748739547026207569446359265024200993747841661884692928926039185964274224841237045619928248330951699007619244530879692563852129885323775823816451787955743942968401187507702618237082254283484203161006940664144806744142758756632646039371103714891470816121641325719797534020540250766889785919814382
n2 = 22160567763948492895090996477047180485455524932702696697570991168736807463988465318899280678030104758714228331712868417831523511943197686617200545714707332594532611440360591874484774459472586464202240208125663048882939144024375040954148333792401257005790372881106262295967972148685076689432551379850079201234407868804450612865472429316169948404048708078383285810578598637431494164050174843806035033795105585543061957794162099125273596995686952118842090801867908842775373362066408634559153339824637727686109642585264413233583449179272399592842009933883647300090091041520319428330663770540635256486617825262149407200317
c2 = 19690520754051173647211685164072637555800784045910293368304706863370317909953687036313142136905145035923461684882237012444470624603324950525342723531350867347220681870482876998144413576696234307889695564386378507641438147676387327512816972488162619290220067572175960616418052216207456516160477378246666363877325851823689429475469383672825775159901117234555363911938490115559955086071530659273866145507400856136591391884526718884267990093630051614232280554396776513566245029154917966361698708629039129727327128483243363394841238956869151344974086425362274696045998136718784402364220587942046822063205137520791363319144
n3 = 30411521910612406343993844830038303042143033746292579505901870953143975096282414718336718528037226099433670922614061664943892535514165683437199134278311973454116349060301041910849566746140890727885805721657086881479617492719586633881232556353366139554061188176830768575643015098049227964483233358203790768451798571704097416317067159175992894745746804122229684121275771877235870287805477152050742436672871552080666302532175003523693101768152753770024596485981429603734379784791055870925138803002395176578318147445903935688821423158926063921552282638439035914577171715576836189246536239295484699682522744627111615899081
c3 = 17407076170882273876432597038388758264230617761068651657734759714156681119134231664293550430901872572856333330745780794113236587515588367725879684954488698153571665447141528395185542787913364717776209909588729447283115651585815847333568874548696816813748100515388820080812467785181990042664564706242879424162602753729028187519433639583471983065246575409341038859576101783940398158000236250734758549527625716150775997198493235465480875148169558815498752869321570202908633179473348243670372581519248414555681834596365572626822309814663046580083035403339576751500705695598043247593357230327746709126221695232509039271637
e = 65537
def fermatfactor(N):
a = isqrt(N)
while True:
a += 1
b = isqrt(a**2-N)
if b**2 == a**2-N:
return a+b
def decrypt(p, n, c):
q = n//p
d = pow(e, -1, (p-1)*(q-1))
flag = pow(c, d, n)
return bytes.fromhex(f"{flag:02x}").decode()
part1 = decrypt(fermatfactor(n1), n1, c1)
part2 = decrypt(gcd(n2, n3), n2, c2)
part3 = decrypt(gcd(n2, n3), n3, c3)
print(part1 + part2 + part3)
# utflag{oh_no_it_didnt_work_</3_i_guess_i_can_just_use_standard_libraries_in_the_future}
simple signature
No source but it appears to be a decryption oracle.
So for any message (m) we send, we get back a signature (s), s = pow(m, d, n)
Then we need to create our own valid m, s signature pair.
We can use the RSA malleability property, consider for 6=3*2
, sig(6) = sig(3)*sig(2) mod n
Proof (using the power exponent rule (a*b)^x = a^x * b^x
)
$ nc betta.utctf.live 4374
Welcome to the signature generator!
This service generates signatures for nonnegative integer messages.
Today's RSA parameters are:
n = 20415066230179189226655149264537724147591623624682730761955140648778458402275606901723638269049655682284186086013650840509742483729652034809730177586632594747781359787931948136563662241157665655079386350527191125942248412493255288112534700043550482131438526150766323112631450768427358290754311376094458343655848933916718735904867387641440343528377748904548047138665899572706348813362215907600477751846294333476967752293881899348568991956250396195377189890271961160935689222345939545452017243585640087717905789811973137909381106129650029732537812690717069557833867022238912126568492338326301340321579085301469466157901
e = 65537
Enter a message as an integer (enter 0 to stop): 2
Your signature is: 14861455497997810428992714710321905197785955247214974695909267013498416860956225522681777491795028714304534748063041190888439170024909315271920586343279608133344963404601102051830522156862988923384375576503137541439671950403929956668586568506632168323792701275036018450860168575752225895266156031019607665460336770196107384857811717399040843130937799239377711995009124357660910806378189901732448977177734343088063609366402869739292144283896150538095965390123574449001982679033525801996644589966651482052230695530781955939122624883649665824070501302689707885200895299548716061789266429556455214194860113009495006549104
Enter a message as an integer (enter 0 to stop): 3
Your signature is: 10030315394465441189917971931712991957220343656574034043145750864826490178909590971112386040933927618664650620705922800477920038051448416412260816806921888252178537551233322038818126971889309193568932337971929152774135930527322121360496902714690638124006583742369963844361353772536759281273894587020946593361932738530589437754986990016579023647120305037716526630431031491572215466365274669596683988252369562870925205088028745538098608799984354288010978171948460376551500120988140147296489566455755181267017850570908115283233559356398249882778633195088221434770575788840957270951696786000967045663490743540696235569183
Enter a message as an integer (enter 0 to stop): 0
Now, come up with your own pair!
Enter a message: 6
Enter a signature:
Now we calculate what to send:
>>> n = 20415066230179189226655149264537724147591623624682730761955140648778458402275606901723638269049655682284186086013650840509742483729652034809730177586632594747781359787931948136563662241157665655079386350527191125942248412493255288112534700043550482131438526150766323112631450768427358290754311376094458343655848933916718735904867387641440343528377748904548047138665899572706348813362215907600477751846294333476967752293881899348568991956250396195377189890271961160935689222345939545452017243585640087717905789811973137909381106129650029732537812690717069557833867022238912126568492338326301340321579085301469466157901
>>> s2 = 14861455497997810428992714710321905197785955247214974695909267013498416860956225522681777491795028714304534748063041190888439170024909315271920586343279608133344963404601102051830522156862988923384375576503137541439671950403929956668586568506632168323792701275036018450860168575752225895266156031019607665460336770196107384857811717399040843130937799239377711995009124357660910806378189901732448977177734343088063609366402869739292144283896150538095965390123574449001982679033525801996644589966651482052230695530781955939122624883649665824070501302689707885200895299548716061789266429556455214194860113009495006549104
>>> s3 = 10030315394465441189917971931712991957220343656574034043145750864826490178909590971112386040933927618664650620705922800477920038051448416412260816806921888252178537551233322038818126971889309193568932337971929152774135930527322121360496902714690638124006583742369963844361353772536759281273894587020946593361932738530589437754986990016579023647120305037716526630431031491572215466365274669596683988252369562870925205088028745538098608799984354288010978171948460376551500120988140147296489566455755181267017850570908115283233559356398249882778633195088221434770575788840957270951696786000967045663490743540696235569183
>>> s6 = (s3*s2) % n
>>> s6
17435952766914387911291485376840670713446759280182677931575017375551393881460452173242599774852842926532103029055319160417504801753691107239893764721188558330426143029168688706883330201341828598178825919830617754559945231056909700349376781696713891707951497362694812945615615285362781238128281495668881540420318964376824949014067654568305676982949209452709019534431426286232868135325516587057226342927943472804469293001387874188610228510272653725493469429346078472506984927306477172326998242079948400786972344796094869743901461488232364593381595798556486350530942322347648969483385320703721518546731489186759314103440
Enter a signature: 17435952766914387911291485376840670713446759280182677931575017375551393881460452173242599774852842926532103029055319160417504801753691107239893764721188558330426143029168688706883330201341828598178825919830617754559945231056909700349376781696713891707951497362694812945615615285362781238128281495668881540420318964376824949014067654568305676982949209452709019534431426286232868135325516587057226342927943472804469293001387874188610228510272653725493469429346078472506984927306477172326998242079948400786972344796094869743901461488232364593381595798556486350530942322347648969483385320703721518546731489186759314103440
Congrats! Here is the flag: utflag{a1m05t_t3xtb00k_3x3rc153}
Cryptordle
First I downloaded this wordlist:
wget https://gist.githubusercontent.com/scholtes/94f3c0303ba6a7768b47583aff36654d/raw/d9cddf5e16140df9e14f19c2de76a0ef36fd2748/wordle-La.txt
Then you can brute what n would be for some random chosen input words, and if it matches then it’s a possible answer.
Do this a few times and see which possibilities appear for every try.
from pwn import remote
def send(io, word):
io.recvline()
io.sendline(word.encode())
return io.recvline()
def brute_possible(guess, n):
possible = set()
for answer in open('wordle-La.txt').readlines():
answer = answer.strip()
response = 1
for x in range(5):
a = ord(guess[x]) - ord('a')
b = ord(answer[x]) - ord('a')
response = (response * (a-b)) % 31
if response == n:
possible.add(answer)
return possible
def solve():
io = remote("betta.utctf.live", 7496)
try:
for _ in range(3):
possible_sets = []
for guess in ["aaaaa", "bbbbb", "ccccc", "ddddd", "eeeee"]:
n = int(send(io, guess))
possible_sets.append(brute_possible(guess, n))
a, b, c, d, e = possible_sets
intersection = a.intersection(b, c, d, e)
print(intersection)
for answer in intersection:
print(send(io, answer))
print(io.recv())
print(io.recvline())
return True
except:
print('unlucky, trying again')
io.close()
while True:
if solve():
break
# utflag{sometimes_pure_guessing_is_the_strat}
Insanity Check: Reimagined
Download the website’s favicon: https://utctf.live/files/e93f3664ce530fcb07151f78ee99dd11/favicon.svg
Delete this line:
document.getElementById("center").classList.remove("center");
and edit this:
.center {
animation: blink 120s infinite;
animation-delay: 1s;
}
Then open it with a web browser and you should see the square blinking morse code.
info = []
for line in open('favicon.svg').readlines()[3:208]:
line = line.strip()
p = float(line.split('%')[0]) # percent
c = line.split('#')[1][:4] # color
info.append((p, c))
time = 0.0
light = False
result = ""
for i in range(0, len(info)-1):
p1, c1 = info[i]
p2, c2 = info[i+1]
if light:
time += p2 - p1
if c2 == "FFFF":
if time > 1:
result += " /"
if time > 0.5:
result += " "
time = 0.0
light = False
else:
time += p2 - p1
if c2 == "FFF6":
if time < 0.5:
result += "."
else:
result += '-'
time = 0.0
light = True
print(result)
MORSE = {
'/':'_', '..-.': 'F', '-..-': 'X',
'.--.': 'P', '-': 'T', '...-': 'V',
'-.-.': 'C', '.': 'E', '.---': 'J',
'---': 'O', '-.-': 'K', '..': 'I',
'.-..': 'L', '-.--': 'Y', '.--': 'W',
'....': 'H', '-.': 'N', '.-.': 'R',
'-...': 'B', '--..': 'Z', '-..': 'D',
'--.-': 'Q', '--.': 'G', '--': 'M',
'..-': 'U', '.-': 'A', '...': 'S'
}
flag = ""
for r in result.split():
flag += MORSE[r]
flag = flag[:6] + '{' + flag[7:] + '}'
print(flag)
# UTFLAG{UTCTF_USES_SVG_TO_ITS_FULLEST}
Study Music
About 3 hours in there is morse code of utflag{l0v3th4tdanc3}
: