Challenge Files



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)

\[6^d \equiv (3 \cdot 2)^d \equiv 3^d \cdot 2^d \ \text{ (mod n)}\]
$ 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

https://youtu.be/1Cbaa6dO2Yk

About 3 hours in there is morse code of utflag{l0v3th4tdanc3}:

image