Snails - CryptoCTF

This challenge implements ECDSA over a custom elliptic curve. The nonces that it generates for ECDSA are 512 bits long, while the order of the curve is 521 bits long. Therefore, the generated nonces are “small” compared to the order of the curve. We will use LLL to exploit this and recover the key. This is the code of the challenge: def sign(msg, skey): h = bytes_to_long(sha512(msg).digest()) k = getRandomNBitInteger(h.bit_length()) P = k * G r = int(P.xy()[0]) % n s = pow(k, -1, n) * (h + r * skey) % n return (r, s) def main(): border = "┃" pr( "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓") pr(border, " Hey! To solve the Snails cryptography challenges one often needs", border) pr(border, " to perform meticulous bit by bit analysis to uncover loopholes ", border) pr(border, " and ultimately extract the high value flag! Good luck ;) 🐌🐌🐌 ", border) pr( "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛") global flag, G, n skey = bytes_to_long(flag) p = 0x013835f64744f5f06c88c8d7ebfb55e127d790e5a7a58b7172f033db4afad4aca1ae1cdb891338cf963b30ff08d6af71327770d00c472c52290a60fb43f1d070025b a = 0x0109ec0177a5a57e7b7890993e11ba1bc7ba63c1f2afd904a1df35d1fda7363ea8e83f3291e25b69dac26d046dc5ba9a42ff74cd7e52c9df5dbe8d4d02755d26b111 b = 0x0037c84047a6cc14e36d180f9b688fe9959cb63f4ac37b22eb24559e83cfc658ff0ab753540b8ab8d85a62dd67aa92f79dec20d28e453d4663ef2882c7b031ddc0b9 n = 0x013835f64744f5f06c88c8d7ebfb55e127d790e5a7a58b7172f033db4afad4aca1aad8763fe2401b5189d1c449547a6b5295586ce30c94852845d468d52445548739 x = 0x00339495fdbeba9a9f695d6e93effeb937609ce2e628958cd59ba307eb3a43c4c3a54b9b951cd593c876df93a9b0ed7d64df641af94668cb594b6a636ae386e1ac1b y = 0x00038389f29ad8c87e79a8b854e78310b72febb6b1840e360b0a43733933529ee6a04f6d7ea0d91104eb83d1162d55c410eca1c7b45829925fb2a9bf9c1232c32972 E = EllipticCurve(GF(p), [a, b]) G = E(x, y) m = '✔✔✔ My signature is the priority'.encode() while True: pr(f"{border} Options: \n{border}\t[S]ign message! \n{border}\t[Q]uit") ans = sc().decode().strip().lower() if ans == 's': pr(border, f'Please send your message: ') msg = sc().strip() if m in msg and len(msg) == 40: r, s = sign(msg, skey) pr(border, f'{r = }') pr(border, f'{s = }') else: die(border, 'Not valid message! Bye!!') elif ans == 'q': die(border, "Quitting...") else: die(border, "Bye...") We note that the order of the curve is 521 bits long:

Mitram - CryptoCTF

This is a bit weird challenge in my opinion. It constructs some exotic matrices, mix them up a little bit and expects us to recover the flag from the output. This is the code: q, v, m = 256, 40, 14 _F = GF(q) def makeup(M, n): for i in range(n): for j in range(i, n): M[i, j] += M[j, i] M[j, i] = 0 return M def mitramap(): _M = [] for s in range(m): M = zero_matrix(_F, v + m, v + m) for i in range(0, v): M[i, (i + s + 1) % v] = _F.random_element() M[i, (i + s) % m + v] = _F.random_element() M = makeup(M, v + m) _M.append(M) return _M def n2F(n): x = _F.gen() e = sum(((n >> _) & 1) * x ** _ for _ in range(8)) return e def F2n(e): x = _F.gen() n = 0 for _ in range(8): n |= (int(e.coefficient(x, _)) << _) return n def embed_secret(msg, v, m): M = random_matrix(_F, v, m) for _ in range(v): M[_, 0] = n2F(msg[_]) return M def maketrig(): return block_matrix([[identity_matrix(_F, v), embed_secret(flag, v, m)], [zero_matrix(_F, m, v), identity_matrix(_F, m)]]) def makepub(F, S): S = S.submatrix(0, v, v, m) Z = zero_matrix(_F, m, v) return [ block_matrix([ [G := M.submatrix(0, 0, v, v), (G + G.transpose()) * S + (H := M.submatrix(0, v, v, m))], [Z, makeup(S.transpose() * G * S + S.transpose() * H, m)] ]) for M in F[:m] ] F, S = mitramap(), maketrig() P = makepub(F, S) print(f'{dumps(F) = }') print(f'{dumps(P) = }') Let’s forget about the specifics of the mitramap and makeup functions, as I won’t be using them in this solution. Note that the maketrig function creates a random matrix whose first column is the flag. Our goal is to recover this matrix $S$.

Toffee - CryptoCTF

In this challenge, we are given the file Toffee.sage, which implements a custom signature scheme using elliptic curves. First, it generates some parameters: global flag, u, v, k, _n, G skey = bytes_to_long(flag) p = 0xaeaf714c13bfbff63dd6c4f07dd366674ebe93f6ec6ea51ac8584d9982c41882ebea6f6e7b0e959d2c36ba5e27705daffacd9a49b39d5beedc74976b30a260c9 a, b = -7, 0xd3f1356a42265cb4aec98a80b713fb724f44e747fe73d907bdc598557e0d96c5 _n = 0xaeaf714c13bfbff63dd6c4f07dd366674ebe93f6ec6ea51ac8584d9982c41881d942f0dddae61b0641e2a2cf144534c42bf8a9c3cb7bdc2a4392fcb2cc01ef87 x = 0xa0e29c8968e02582d98219ce07dd043270b27e06568cb309131701b3b61c5c374d0dda5ad341baa9d533c17c8a8227df3f7e613447f01e17abbc2645fe5465b0 y = 0x5ee57d33874773dd18f22f9a81b615976a9687222c392801ed9ad96aa6ed364e973edda16c6a3b64760ca74390bb44088bf7156595f5b39bfee3c5cef31c45e1 F = FiniteField(p) E = EllipticCurve(F, [a, b]) G = E(x, y) u, v, k = [randint(1, _n) for _ in ';-)'] Then, we have the main challenge’s loop: while True: pr(f"{border} Options: \n{border}\t[G]et toffee! \n{border}\t[S]ign message! \n{border}\t[Q]uit") ans = sc().decode().strip().lower() if ans == 'g': pr(border, f'Please let me know your seed: ') _k = sc().decode().strip() try: _k = int(_k) except: die(border, 'Your seed is not valid! Bye!!') pr(f'{toffee(u, v, _k) = }') elif ans == 's': pr(border, f'Please send your message: ') msg = sc().strip() r, s = sign(msg, skey) pr(border, f'{r = }') pr(border, f'{s = }') elif ans == 'q': die(border, "Quitting...") else: die(border, "Bye...") The secret key used to sign the messages is the flag. We can: