We are given \(N\), \(e\), and ciphertext in the context of RSA.
We are also given an oracle which solves the dicrete log problem modulo
\(N\), in other word, outputs a \(x\) satisfy
\[ a^x \equiv b \pmod{N}\]
given input \(a\) and \(b\). There were no source code and we have
no idea how it computes discrete log.
Solution
The first thing that came into my mind is that \(p\) and \(q\) must be special so that the discrete
log problem can be solved easily. However, it is just too guessy and I
quickly give up on this. Then I realized that if we give them \((a, 1)\) as input, they will give us an
\(x\) such that
\[a^x \equiv 1 \pmod{N}\]
Therefore, \(x \mid \phi(N)\). Since
\(\phi(N)\) is really close to \(N\), we can recover it from \(x\) easily. It turned out the server just
give us \(x = 0\), which is trivial.
However, with a simple modification, we can solve the problem! We send
\((a, n - 1)\) as input, and they
respond with a \(x\) such that
\[a^x \equiv -1 \pmod {N}\]
We have \(a^{2x} \equiv 1 \pmod
{N}\) and \(x\) cannot be \(0\)! As such discrete log may not always
exist, we try several values of \(a\)
and wait for a valid \(x\). With \(x\), we find a multiple that is smaller but
really close to \(N\) and that will be
the \(\phi(N)\). The rest is
trivial.
from pwn import * from decimal import * getcontext().prec = 500
r = remote('35.244.0.8', 34712)
r.recvuntil(b"Mod: ") N = int(r.recvline().strip()) r.recvuntil(b"(e=65537)\n") flag = int(r.recvline().strip()) r.recvuntil(b"base:") e = 65537
for i in [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53]: print(i) r.sendline(str(i).encode()) r.sendline(str(N - 1).encode()) r.recvuntil(b"number: ") res = r.recvline() if res.startswith(b'discrete log is not found'): continue
tmp = len('Here is the discrete log: ') res = int(res[tmp:].decode('ascii')) phi = N // res * res PLUS = N + 1 - phi _ = Decimal(PLUS * PLUS - N * 4) ** (1 / Decimal(2)) _ = int(_) p = (PLUS + _) // 2 q = PLUS - p assert p * q == N e = 65537 d = pow(e, -1, (p - 1) * (q - 1)) m = pow(flag, d, N) print(bytes.fromhex(hex(m)[2:])) exit(0)
Square Sum
Description
We are given \(n\), \(S = \left(\frac{p - 1}{2}\right)^2 + \left(\frac{q
- 1}{2}\right)^2\) such that \(n =
pq\), then we have a bunch of calculations
1 2 3 4 5 6 7
n2 = n * n k = randint(1, n-1) g = 1 + k*n r = randint(2, n2-1)
cmsg = (pow(g, bytes_to_long(MSG), n2) * pow(r, n2, n2)) % n2 c = (bytes_to_long(FLAG) * pow(r, n, n2)) % n2
and we
only know cmsg and c.
Solution
The first step is to factorize \(n\). It is quite straight forward with a
little math.
The second step is to break down those weird calculations. We can get
r % n by taking the first equation modulo \(n\) instead of \(n^2\) as we can drop the term with \(g\):
\[r^{n^2} \equiv \text{cmsg}
\pmod{n}\]
Since the factorization of \(n\) is
known, we can calculate r = pow(cmsg, pow(n^2, -1, phi))
where phi = (p - 1) * (q - 1). Move on to the next line,
notice that
\(x^n\) is periodic modulo \(n^2\) with period \(n\). Therefore, we can directly calculate
FLAG = c * pow(r, -n, n2) % n2 without knowing
r % n2. The message is not recover-able because of the
randomness of \(k\), but the flag is
retrieved.
_ = 4 * halfpq_sqr_sum - 2 + 2 * n + 1 PLUS = int(Decimal(_).sqrt()) + 1 assert PLUS % 2 == 0 MINUS = int(Decimal(PLUS * PLUS - 4 * n).sqrt()) assert MINUS % 2 == 0 p = (PLUS + MINUS) // 2 q = PLUS - p assert p * q == n
phi = (p - 1) * (q - 1) rnn = cmsg % n r = pow(rnn, pow(n2, -1, phi), n) assertpow(r, n2, n) == cmsg % n flag = c * pow(r, -n, n2) % n2 assert (flag * pow(r, n, n2)) % n2 == c % n2 print(bytes.fromhex(hex(flag)[2:]))
ECSign
Description
The main part of this problem is to recover the private key with the
EC sign. We are given 3 signatures of the same message, with the same
r (which is really sus) and nonce k,
k^0xffffffff, k^0xffffffff00000000. The
algorithm of signing is
\[\text{sig} \equiv \frac{h + d_Ar}{k}
\pmod{n}\]
where \(d_A\) is the private key,
and \(h\) is the hash of the
message.
Solution
Notice that the numerator of the signature is fixed in all 3
signatures. Let \(\alpha = h + d_Ar\),
we have
\[k_i \equiv
\frac{\alpha}{sig_i}\]
Since \(k_i\)s do not differ too
much, both \(\alpha(sig_1^{-1} -
sig_2^{-1})\) and \(\alpha(sig_1^{-1} -
sig_3^{-1})\) have a small absolute value modulo \(n\). Let \(x =
sig_1^{-1} - sig_2^{-1}\) and \(y =
sig_1^{-1} - sig_3^{-1}\), then there exist small \((a, b)\) such that \(n \mid ax + by\). Therefore, we can
construct a lattice:
import hashlib import random from Crypto.Util.number import inverse, bytes_to_long, long_to_bytes from Crypto.Random import get_random_bytes from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from ecdsa import NIST256p from ecdsa.ellipticcurve import Point
message = b"ECDSA prevents forging messages"
curve = NIST256p G = curve.generator n = int(curve.order)
\(N\) is the product of 1024-bit
primes \(p\) and \(q\), and \(p'\) is a an approximation of \(p\) such that \(|p' - p| < 2^{80}\). We are given
\(N\) and a quartic polynomial \(f\) such that \(N
\mid f(p')\). The goal is to decrypt the ciphertext encrypted
by RSA with \(N\).
Solution
This problem is the basic form of Coppersmith Attack. I used the script
by mimoo to solve the challenge.
# display matrix picture with 0 and X defmatrix_overview(BB, bound): for ii inrange(BB.dimensions()[0]): a = ('%02d ' % ii) for jj inrange(BB.dimensions()[1]): a += '0'if BB[ii,jj] == 0else'X' a += ' ' if BB[ii, ii] >= bound: a += '~' print(a)
defcoppersmith_howgrave_univariate(pol, modulus, beta, mm, tt, XX): """ Coppersmith revisited by Howgrave-Graham finds a solution if: * b|modulus, b >= modulus^beta , 0 < beta <= 1 * |x| < XX """ # # init # dd = pol.degree() nn = dd * mm + tt
# # checks # ifnot0 < beta <= 1: raise ValueError("beta should belongs in (0, 1]")
ifnot pol.is_monic(): raise ArithmeticError("Polynomial must be monic.")
# # calculate bounds and display them # """ * we want to find g(x) such that ||g(xX)|| <= b^m / sqrt(n) * we know LLL will give us a short vector v such that: ||v|| <= 2^((n - 1)/4) * det(L)^(1/n) * we will use that vector as a coefficient vector for our g(x) * so we want to satisfy: 2^((n - 1)/4) * det(L)^(1/n) < N^(beta*m) / sqrt(n) so we can obtain ||v|| < N^(beta*m) / sqrt(n) <= b^m / sqrt(n) (it's important to use N because we might not know b) """ if debug: # t optimized? print("\n# Optimized t?\n") print("we want X^(n-1) < N^(beta*m) so that each vector is helpful") cond1 = RR(XX^(nn-1)) print("* X^(n-1) = ", cond1) cond2 = pow(modulus, beta*mm) print("* N^(beta*m) = ", cond2) print("* X^(n-1) < N^(beta*m) \n-> GOOD"if cond1 < cond2 else"* X^(n-1) >= N^(beta*m) \n-> NOT GOOD") # bound for X print("\n# X bound respected?\n") print("we want X <= N^(((2*beta*m)/(n-1)) - ((delta*m*(m+1))/(n*(n-1)))) / 2 = M") print("* X =", XX) cond2 = RR(modulus^(((2*beta*mm)/(nn-1)) - ((dd*mm*(mm+1))/(nn*(nn-1)))) / 2) print("* M =", cond2) print("* X <= M \n-> GOOD"if XX <= cond2 else"* X > M \n-> NOT GOOD")
# solution possible? print("\n# Solutions possible?\n") detL = RR(modulus^(dd * mm * (mm + 1) / 2) * XX^(nn * (nn - 1) / 2)) print("we can find a solution if 2^((n - 1)/4) * det(L)^(1/n) < N^(beta*m) / sqrt(n)") cond1 = RR(2^((nn - 1)/4) * detL^(1/nn)) print("* 2^((n - 1)/4) * det(L)^(1/n) = ", cond1) cond2 = RR(modulus^(beta*mm) / sqrt(nn)) print("* N^(beta*m) / sqrt(n) = ", cond2) print("* 2^((n - 1)/4) * det(L)^(1/n) < N^(beta*m) / sqrt(n) \n-> SOLUTION WILL BE FOUND"if cond1 < cond2 else"* 2^((n - 1)/4) * det(L)^(1/n) >= N^(beta*m) / sqroot(n) \n-> NO SOLUTIONS MIGHT BE FOUND (but we never know)")
# warning about X print("\n# Note that no solutions will be found _for sure_ if you don't respect:\n* |root| < X \n* b >= modulus^beta\n") # # Coppersmith revisited algo for univariate #
# change ring of pol and x polZ = pol.change_ring(ZZ) x = polZ.parent().gen()
# compute polynomials gg = [] for ii inrange(mm): for jj inrange(dd): gg.append((x * XX)**jj * modulus**(mm - ii) * polZ(x * XX)**ii) for ii inrange(tt): gg.append((x * XX)**ii * polZ(x * XX)**mm) # construct lattice B BB = Matrix(ZZ, nn)
for ii inrange(nn): for jj inrange(ii+1): BB[ii, jj] = gg[ii][jj]
# display basis matrix if debug: matrix_overview(BB, modulus^mm)
# LLL BB = BB.LLL()
# transform shortest vector in polynomial new_pol = 0 for ii inrange(nn): new_pol += x**ii * BB[0, ii] / XX**ii
# test roots roots = [] for root in potential_roots: if root[0].is_integer(): result = polZ(ZZ(root[0])) if gcd(modulus, result) >= modulus^beta: roots.append(ZZ(root[0]))
# return roots
N = 14696994372012287266063217731371465578678715866132272122977296158042664711913985707735240860887006104199660244194252732513872446957209707813718992065870009198350392025823708604809843097942852887153574791868863759677596709374574270980384013350507776841394348069047979081285459868079488308763109757234951918205929827395300302835788692873505186477300098666817569700699255710985965984118993464275972585327700080430955818549340143244083420792258768933607896874184152407531531128653324411725754823245939716911634247425735766453356062475072240421779566396468043043082651606158198150758975259067495695864660578493393106762631 ZmodN = Zmod(N) e = 65537 c = 9750527994091121091786816841463772484970643350548409420602551052460114750376765234779160399774157114083542404212321500558790661890217330206975918220078382877833585674657160688057126517189623419338705483072993529673151815011674742481352311071675409754487654783078829059793691828971457847280715985552753121754286619544143717362922120309444897947012514213521135742738481921772607488909188240043132021160662803034095731089505604348170622399204914184546012322270024791178809723808523030967238028593150206277299911554652571555702651337880252089368970936855513446902610214801472036929625184874736910343726791265452474611050 seq = [158752811818711918978608809724046452901335596022391874487974890304998816699298753351923479802743256111682434826558127366354372655672162233383691616336809056589770476184830602519936915124324990526086510115052439516943494324074208205263728559181179209273336271456505648791496885861665659702772878794453617597935, 128112039454566836699268817573729682217733580513471579004092384875666907545736760938092529139116761787964524453230653391142195587208637498768853852534066440912659539891579333760989970185793933487448394446383099379350084438155354954554485354584606335545683635861714347785116146996111161679962531883524512245756, 125394898446858383529773530925952405484923255072498779305263565535739539843809683956661976383945903145655388058775285644922423567418124108556099444145910243884249123272753835196094595735828560593886549706202821360729966212919836071016187794411200454682068981836300681098224156531639842209297777220250045105015, 173287806459060855056978404566038662455264340360403434058797921467696881992461886820974507593896975956673635068182667307551397235117090256800473173760922218094509836540044524144073307976531893785817268555430923542035419459537615158405264410714554348532449071393153250670293668267831809663639016079737049993532, 110367489208453437299284319505293878991095171003329152569229822459210424547301175160255795581820578959629690051139383553782954098870270063851900045993344035739536369769062409357695761165417611332640635883761689554713983143014247113573513500943553569138485111921531165356036728439854269183574182361770067877382] val = 4587129147169223135177082248247577247287230696838250360893151376678832017794662311496645432391215540389204568010806710075971796695505304027066300219073996196566364920871649278153101805335618485690679631760671100407668295798900270041393585528052410778685339781812407359874425601526059126165668484671578858665132491434454904136915398866378458071913624317667264519275308368507047649155997427360113558401018558028857800979164191767840984644802095146231460160633834780847547723464319670338864485176202467124809407088132810097750468091447438249060863489382657696069166135186865875148261692352214220481602521790532932330766
seq[4] -= val iseq0 = pow(seq[0], -1, N) seq = [x * iseq0 % N for x in seq]