withopen("dump.txt") as f: nums = [int(l.strip()) for l in f] nums = [ 211286818345627549183608678726370412218029639873054513839005340650674982169404937862395980568550063504804783328450267566224937880641772833325018028629959635 ] + nums
defget_n(nums): n_muls = [get_n_mul(*nums[i : i + 4]) for i inrange(0, len(nums) - 4)] return reduce(gcd, n_muls)
deflcg_recover(nums): n = get_n(nums) m = (nums[2] - nums[1]) * pow(nums[1] - nums[0], -1, n) % n c = (nums[1] - nums[0] * m) % n assertall((nums[i + 1] == (nums[i] * m + c) % n for i inrange(len(nums) - 1))) return m, c, n
m, c, n = lcg_recover(nums) print(f"{m = }") print(f"{c = }") print(f"{n = }")
key = RSA.import_key(open("public.pem").read()) primes = [ 6964450570065198027118448061962599095969292963451720606983076074110302247620361094481177336650245972387693336005120667581467488333027638118705008539730161, 7109513965197777738721520789907575526136034746458175895732109137546481171865697500840378508918180196617036069476257074027995108145765959244529836549641993, 7758154508395114989879950785752176334836698656046420925847088416774817812432659191047047708438059303423539646671584835156522194907603278157965955707005259, 7955799783393765300984709531409964215101221994176999027251797780817998458393197830706475509797724088969852653876011767020597895098981645731673988999811477, 8049144504671772187556553424691159710790891318598720019107386049196471499825470743225448919013622833022782797600720245991463382370047241689718971308017941, 7987778116986381838775258565312725165121950830999324816847582213568425816373564731712259704074323632466001514291790772213690155362407596378791328567707167, 7742807139391426401434854463005769878789338221498371127433759280276903959426344243059697341236161854231155869200509304579276312983038493231864184737415279, 7008809633477108007104709300243139302645615208732682375658473606356527935080408259873753634627922136035847144744200184716721386652578226493417857596011803, ] assert reduce(mul, primes) == key.n phi = reduce(lambda x, y: x * y, [p - 1for p in primes]) d = pow(key.e, -1, phi) assertpow(2, phi, key.n) == 1 withopen("flag.txt", "rb") as f: c = int.from_bytes(f.read(), "little") m = pow(c, d, key.n) print(m.to_bytes((m.bit_length() + 7) // 8, "big")) # CTF{C0nGr@tz_RiV35t_5h4MiR_nD_Ad13MaN_W0ulD_b_h@pPy}
MYTLS
這題有個在 Python 自己實作的類似 MTLS 的 protocol,並且有提供幾把
key:
ca-crt.pem: CA 的 public key
guest-ecdhkey.pem: guest 的 private key
guest-ecdhcert.pem: guest 的 certificate
server-ecdhcert.pem: server 的 certificate
admin-ecdhcert.pem: admin 的 certificate
certificate 相當於是被 CA sign 過的 public key
它的流程大概是這樣:
sequenceDiagram
Client->Server: Connection established
Server->>Client: Server Cert
Note over Client: Verify Server Cert
Client->>Server: Client Cert
Note over Server: Verify Client Cert
Note over Client: Generate client random
Client->>Server: Client random
Note over Client: Generate <br> ephemeral key pair
Client->>Server: Ephemeral client public key
Note over Server: Generate server random
Server->>Client: Server random
Note over Server: Generate <br> ephemeral key pair
Server->>Client: Ephemeral server public key
Note over Client, Server: Exchange ephemeral key (ECDH) <br> to get ephemeral secret
Note over Client, Server: Exchange key certificate (ECDH) <br> to get shared secret
Note over Client, Server: Derive key using two secrets and random
Client->>Server: HMAC(key, client_success_msg)
Note over Server: Verify hmac
Server->>Client: HMAC(key, server_success_msg)
Note over Client: Verify hmac
Client->Server: TLS Connection established
和 server 建立完加密連線後它會判斷 client certificate 的 subject
是否包含 CN=admin.mytls,如果是的話就會把 flag 傳給
client。不過如果要用 admin-ecdhcert.pem
連線的話一般要有對應的 private key 才行,而自己新增一個 public key
也做不到,因為 server 會用 CA public key 檢查 client 傳送過去的
certficate 是否是被 CA sign 過的。
import binascii from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import hmac from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.kdf.hkdf import HKDF import hashlib import os from secrets import token_hex import string from pwn import remote, context
withopen("ca-crt.pem", "rb") as ca_file: ca = x509.load_pem_x509_certificate(ca_file.read()) withopen("server-ecdhcert.pem", "rb") as server_cert_file: server_cert_content = server_cert_file.read() server_cert = x509.load_pem_x509_certificate(server_cert_content) withopen("guest-ecdhcert.pem", "rb") as f: client_cert_content = f.read() client_cert = x509.load_pem_x509_certificate(client_cert_content) withopen("guest-ecdhkey.pem", "rb") as f: client_key = serialization.load_pem_private_key(f.read(), None, default_backend())
io = remote("mytls.2023.ctfcompetition.com", 1337) io.recvuntil(b"Please provide the client certificate in PEM format:\n") io.sendline(client_cert_content)
# Generate client random client_ephemeral_random = token_hex(16).encode() # not really io.recvuntil(b"Please provide the ephemeral client random:") io.sendline(client_ephemeral_random)
# if our guess is right, then two hash should be the same
target_file = b"/app/server-ecdhkey.pem"
deforacle(secret): # we do this twice as the server returns previous hash send_encrypted(io, target_file) send_encrypted(io, secret) recv_encrypted(io) recv_encrypted(io) recv_encrypted(io)
whileTrue: ss = [known_content + c.encode() for c in charset] hs = list(oracle_batch(ss)) for secret, h inzip(ss, hs): if h == target_hash: known_content = secret break else: print("Either EOF or charset is wrong") break print(known_content) print(known_content.decode())
我知道我沒檢查 server certificate,因為我是直接從 fs 讀 server
certificate 而不是從 socket 讀,所以沒差
等知道 server private key 之後要想想怎麼拿到 flag,因為檢查一下各個
certificate 可知唯一 subject 能過那個檢查的只有
admin-ecdhcert.pem,而我們剛剛得到的 private key 是 server
的而非 admin 的,應該沒用對吧...?
import binascii from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import hmac from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.kdf.hkdf import HKDF import hashlib import os from secrets import token_hex import string from pwn import remote, context
withopen("ca-crt.pem", "rb") as ca_file: ca = x509.load_pem_x509_certificate(ca_file.read()) withopen("server-ecdhcert.pem", "rb") as server_cert_file: server_cert_content = server_cert_file.read() server_cert = x509.load_pem_x509_certificate(server_cert_content) withopen("guest-ecdhcert.pem", "rb") as f: client_cert_content = f.read() client_cert = x509.load_pem_x509_certificate(client_cert_content) withopen("guest-ecdhkey.pem", "rb") as f: client_key = serialization.load_pem_private_key(f.read(), None, default_backend()) withopen("server-ecdhkey.pem", "rb") as f: server_key = serialization.load_pem_private_key(f.read(), None, default_backend()) withopen("admin-ecdhcert.pem", "rb") as f: admin_cert_content = f.read() admin_cert = x509.load_pem_x509_certificate(admin_cert_content)
context.log_level = "debug"
io = remote("mytls.2023.ctfcompetition.com", 1337) io.recvuntil(b"Please provide the client certificate in PEM format:\n") io.sendline(admin_cert_content)
# Generate client random client_ephemeral_random = token_hex(16).encode() # not really io.recvuntil(b"Please provide the ephemeral client random:") io.sendline(client_ephemeral_random)
M = prod(p) y = Zmod(M)["y"].gen() f = (x + y * q).monic() for b inrange(900, 1500, 50): rs = f.small_roots(X=2**b, beta=0.40, epsilon=0.03) if rs: # I found answer at b = 1450 print(b, rs) break fs = [p for p, _ in gcd(ZZ(f(rs[0])), M).factor()] bits = [] for pp in p: if pp in fs: bits.append(1) else: bits.append(0) bits.reverse() flag = bytes( [int("".join(map(str, bits[i : i + 7])), 2) for i inrange(0, len(bits), 7)] )[::-1] print(flag) # CTF{w0W_c0Nt1nUed_fr4Ct10ns_suR3_Ar3_fUn_Huh}
CURSVED
這題簡單來說是基於
上一條 上的 group 所弄一個類 eddsa
signature,目標是直接 forge signature。
這種題目我已經看過好多遍了,它的
order 是 代表它大概有個
homomorphism 可以把原本 curve point map 到 ,這樣可能比較好做 DLP。
from sage.allimport * from pwn import remote from chal import Curve, Priv from subprocess import check_output import ast
# generate cado-nfs snapshot with: # cado-nfs.py -dlp -ell 11768463700042336292273890180302660989254097664036009733585033009225459108313 target=6995199833182080318718269901795376783328546073433342063509926122404606405798 23536927400084672584547780360605321978508195328072019467170066018450918216627 # and find the message starting with `If you want to compute one or several new target(s)` to get the snapshot file # target probably doesn't matter snapshot = "/tmp/cado.8enuuo06/p75.parameters_snapshot.0"
p = 0x34096DC6CE88B7D7CB09DE1FEC1EDF9B448D4BE9E341A9F6DC696EF4E4E213B3 ell = 11768463700042336292273890180302660989254097664036009733585033009225459108313 assert2 * ell == p - 1 d = 3 F = GF(p) sD = F(3).sqrt() phi = lambda x, y: x - sD * y g = phi(0x2, 0x1)
withopen("output.txt") as f: pk = ast.literal_eval(f.readline()) ct = ast.literal_eval(f.readline())
deffind_ortho_zz(*vecs): assertlen(set(len(v) for v in vecs)) == 1 L = block_matrix(ZZ, [[matrix(vecs).T, matrix.identity(len(vecs[0]))]]) print("LLL", L.dimensions()) nv = len(vecs) L[:, :nv] *= 2**256 L = L.LLL() ret = [] for row in L: if row[:nv] == 0: ret.append(row[nv:]) return matrix(ret)
deffind_key(a): # a=e*s+p*k t1 = find_ortho_zz(a) assert t1 * vector(a) == 0 # we assume that only t1[-1]*s!=0 and t1[-1]*k!=0 # so the t1[:-1] is orthogonal to s and k # therefore s, k are spanned by u1, u2 u1, u2 = find_ortho_zz(*t1[:-1]) # suppose s=x1*u1+x2*u2, k=y1*u1+y2*u2 # a=e*s+p*k=e*(x1*u1+x2*u2)+p*(y1*u1+y2*u2) # =(e*x1+p*y1)*u1+(e*x2+p*y2)*u2 # = v1*u1+ v2*u2 v1, v2 = matrix([u1, u2]).solve_left(vector(a)) print(f"{v1 = }{v2 = }") # now we expect to find integers x1, x2, y1, y2, e, p such that # matrix([ # [x1,y1], # [x2,y2] # ])*vector([e,p])==vector([v1,v2]) # sum(x1*u1+x2*u2)+2==p # so there are three equations and six unknowns
# after some testing, I found that det([[x1,y1],[x2,y2]]) is either 1 or -1 # so we have four equations and six unknowns, which means it is possible to reduce it to an single equation and two unknowns
for det in [1, -1]: R = QQ["x1s, x2s, y1s, y2s, es, ps"] x1s, x2s, y1s, y2s, es, ps = R.gens() f1, f2 = matrix([[x1s, y1s], [x2s, y2s]]) * vector([es, ps]) - vector([v1, v2]) f3 = x1s * y2s - x2s * y1s - det f4 = sum(x1s * u1 + x2s * u2) + 2 - ps gb = R.ideal([f1, f2, f3, f4]).groebner_basis() mul = reduce(lcm, [c.denom() for c, _ in gb[1]]) eq = gb[1].resultant(f4, ps) * mul # this equations appear to be a linear equation in x1, x2 # so we can solve it using LLL as x1, x2 are small print(eq) L = matrix( QQ, [ [eq.constant_coefficient(), 1, 0, 0], [eq.coefficient({x1s: 1}), 0, 1, 0], [eq.coefficient({x2s: 1}), 0, 0, 1], ], ) bounds = [1, 1, 2**66, 2**66] scale = [2**128 // x for x in bounds] Q = diagonal_matrix(scale) L *= Q L = L.LLL() L /= Q for row in L: if row[1] < 0: row = -row if row[0] == 0and row[1] == 1: x1, x2 = row[2:] # while we should be able to plug the x1, x2 into the ideal the get full solution # but I found that the dimension of the ideal is 1, so we can't do that here # we just solve it manually s = (x1 * u1 + x2 * u2).change_ring(ZZ) p = sum(s) + 2 e_cand1 = a[0] * pow(s[0], -1, p) % p e_cand2 = a[1] * pow(s[1], -1, p) % p if e_cand1 == e_cand2: return s, e_cand1, p