- Published on
BITSCTF 2025 - CRYPTO challenges
Introduction
We solved 3 out of 6 tasks. But one was first blood.
Table of contents
Baby Crypto

Writeup author: ppp45
We know n, e and ct (encrypted flag). We can decrypt any chosen ciphertext except ct.
Since:
decrypt(k*ct) = (k*ct)^d = k^d * ct^d = k^d * pt mod n
we need to choose k for which we know k^d mod n
Because (x^e)^d = x mod n
, we calculate k = 2^e mod n
(in other words we encrypt number 2).
We use the oracle to decrypt k * ct
and receive the plaintext (flag) multiplied by 2.
Flag: BITSCTF{r54_0r4acl3_h4s_g0t_t0_b3_0n3_0f_7h3_3as13st_crypt0_1n_my_0p1n10n_74b15203}
The most wanted lagomorph


Writeup author: umz
ROT-8000
, and the resulting content contained characters from i
to m
, which clearly doesn't match hex format. So, tried iterating through all possible values using characters a-f
. After testing, found that when i=e
, j=d
, k=c
, l=b
, and m=a
, the result was valid. Additionally, i=e
, j=d
, k=c
, l=f
, and m=a
also worked. Given the clue "No More Bunny Business", thought of rabbit encryption and used the rabbit name "dennis" as the password to decrypt and finally obtain the flag. 

U2FsdGVkX1+Kci2LQvPTy06ga66qMTDgoOip6SxH1t7EreImxWCP3RarTyRTU2k3Nrd4vChzcXYKqPZSyl3T
https://www.browserling.com/tools/rabbit-decrypt
Flag: BITSCTF{f3rb_1_kn0w_wh47_w3_4r3_60nn4_d0_70d4y}
Noob RSA returns

We are given a Python script and its output:
from Crypto.Util.number import getPrime, bytes_to_long
def GenerateKeys(p, q):
e = 65537
n = p * q
phi = (p-1)*(q-1)
d = pow(e, -1, phi)
C = 0xbaaaaaad
D = 0xdeadbeef
A= 0xbaadf00d
K = (A*p**2 - C*p + D)*d
return (e, n, K)
def Encrypt():
flag = b"REDACTED" # HEHEHEHEHE
p = getPrime(512)
q = getPrime(512)
e, n, K = GenerateKeys(p, q)
pt = bytes_to_long(flag)
ct = pow(pt, e, n)
print(f"e = {e}")
print(f"n = {n}")
print(f"ct = {ct}")
print(f"K = {K}")
Encrypt()
e = 65537
n = 94391578028846794543970306963076155289398888845132329034244336898352288130614402434536624297683695128972774452047972797577299176726976054101512298009248486464357336027594075427866979990479026404794249095503495046303993475122649145761379383861274918580282133794104162177538259963029805672413580517485119968223
ct = 39104570513649572073989733086496155533723794051858605899505397827989625611665929344072330992965609070817627613891751881019486310635360263164859429539044097039969287153948226763672953863052936937079161030077852648023719781006057880499973169570114083902285555659303311508836688226455433255342509705736365222119
K = 20846957286553798859449981607534380028938425515469447720112802165918184044375264023823946177012518880630631981155207307372567493851397122661053548491580627249805353321445391571601385814438186661146844697737274273249806871709168307518276727937806212329164651501381607714573451433576078813716191884613278097774416977870414769368668977000867137595804897175325233583378535207450965916514442776136840826269286229146556626874736082105623962789881101475873449157946816513513532838149452759771630220014344325387486921028690085783785067988074331005737389865053848981113695310344572311901555735038842261745556925398852334383830822697851
During CTF I came up with a rather inefficient solution but decided to stick with it and focus on other challenges.
If we knew z:
z = (e * d - 1) // phi
we could eliminate other variables and a univariate equation involving single variable p.
z is an integer not greater than e (I didn't try to prove it but calculated it multiple times for different random p and q).
The following script contains equations (assertions) and using the last one we can calculate p using sympy (z3 turned out to be slower in that case).
from Crypto.Util.number import getPrime
A = 0xBAADF00D
B = 0xBAAAAAAD
C = 0xDEADBEEF
e = 65537
def GenerateKeys(p, q):
n = p * q
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)
K = (A * p**2 - B * p + C) * d
return (e, n, K)
p = getPrime(512)
q = getPrime(512)
e, n, K = GenerateKeys(p, q)
# ------------------------------
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)
z = (e * d - 1) // phi
print(f"{z = }")
# ------------------------------
# 1
assert phi == n + 1 - (p + n // p)
# 2
assert d == (z * phi + 1) // e
# 3
assert d == (z * (n + 1 - (p + n // p)) + 1) // e
# 4
assert d == K // ((A * p**2 - B * p + C))
# 5
assert K // ((A * p**2 - B * p + C)) == (z * (1 + n - p - n // p) + 1) // e
# 6
assert K * e == ((A * p**2 - B * p + C)) * (z * (1 + n - p - n // p) + 1)
Basic solver concept:
from sympy import symbols, solve, Eq, isprime
START = 1
END = e
p = symbols("p", integer=True)
for z in range(START, END + 1):
print(f"{z = }")
equation = Eq(K / ((A * p**2 - B * p + C)), (z * (n + 1 - (p + n / p)) + 1) / e)
solutions = solve(equation, p)
for sol in solutions:
if sol.is_integer:
print(f"{int(sol) = }")
exit()
For z = 42677
we recover p which allows us to calculate private key d and decrypt the flag.
p = 10406216443192169173533723167461845081683996237790486467542778667477564930803546070928131853072839096935544813786122096301171127932695303325352097678393621
q = n // p
assert n == p * q
d = pow(e, -1, (p - 1) * (q - 1))
pt = pow(ct, d, n)
print(pt.to_bytes(99))
Flag: BITSCTF{I_H0P3_Y0UR3_H4V1NG_FUN_S0_F4R_EHEHEHEHEHO_93A5B675}