""" Copyright (C) 2012 Bo Zhu http://about.bozhu.me Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from random import Random
# learnt from http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)
defmix_columns(self, s): for i inrange(4): t = s[i][0] ^ s[i][1] ^ s[i][2] ^ s[i][3] u = s[i][0] s[i][0] ^= t ^ xtime(s[i][0] ^ s[i][1]) s[i][1] ^= t ^ xtime(s[i][1] ^ s[i][2]) s[i][2] ^= t ^ xtime(s[i][2] ^ s[i][3]) s[i][3] ^= t ^ xtime(s[i][3] ^ u)
defencrypt_ecb(self, data: bytes) -> bytes: ifnotisinstance(data, (bytes, bytearray)): raise TypeError("data must be bytes-like") iflen(data) % 16 != 0: raise ValueError("data length must be multiple of 16 when pad=False") out = b'' for i inrange(0, len(data), 16): out += self.encrypt(int.from_bytes(data[i : i + 16])).to_bytes(16) return out defchange(self, s=None, k=None): if s: self.Sbox = Random(s).choices(self.Sbox, k=len(self.Sbox)) if k: self.change_key(k)
seed = int.from_bytes(urandom(16)) key = int.from_bytes(urandom(16)) aes = AES(key, seed) print(f'[+] Here is your flag ciphertext (in hex): {aes.encrypt_ecb(pad(flag, 16)).hex()}')
menu = """ --- Menu --- [1] change the S-box [2] encrypt a message [3] reset aes [4] exit """
signal.alarm(60)
for _ inrange(300): print(menu) match x:= input('[x] > '): case'1': s = int(input('[x] your seed: ') or0, 16) orNone k = int(input('[x] your key: ') or0, 16) orNone aes.change(s, k) print('[+] changed!') case'2': msg = bytes.fromhex(input('[x] your message: ')) print(f'[+] Here is your ciphertext (in hex): {aes.encrypt_ecb(pad(msg, 16)).hex()}') case'3': aes = AES(key, seed) print('[+] reset!') case'4': print('[+] Goodbye!') exit() case _: print('[-] Invalid option!')
from os import urandom from random import Random import operator from pwn import * from Crypto.Util.Padding import unpad from AES import AES, matrix2text, text2matrix, xtime STAGE1_SEED = 3313 STAGE1_PAIRS = [(9, 48), (9, 87)]
print("[*] stage2+3: collect PFA samples and intersect") menu_reset(io) recovered_tables = [[set(range(256)) for _ inrange(256)] for _ inrange(16)]
for fault_index, fault_seed inenumerate(PFA_SEEDS): menu_change(io, s=fault_seed) msg = urandom(16 * PFA_BLOCKS) ct = menu_encrypt(io, msg) observed = [set() for _ inrange(16)] for offset inrange(0, len(ct), 16): block = ct[offset:offset + 16] iflen(block) < 16: break for j, value inenumerate(block): observed[j].add(value) missing_cipher = [ALL_BYTES - values for values in observed] idx = Random(int(fault_seed)).choices(range(256), k=256) missing_y = ALL_BYTES - set(idx) for y in missing_y: for j inrange(16): recovered_tables[j][y] &= missing_cipher[j] print(f" seed {fault_index + 1:02d}/{len(PFA_SEEDS)} done; missing_y={len(missing_y)}") menu_reset(io)
for j inrange(16): for y inrange(256): iflen(recovered_tables[j][y]) != 1: raise ValueError(f"PFA intersection not singleton at byte={j}, y={y}, size={len(recovered_tables[j][y])}")
tables = [[next(iter(recovered_tables[j][y])) for y inrange(256)] for j inrange(16)] print("[+] recovered all T_j[y] singletons")
print("[*] stage4: brute Delta and rebuild original P") best = None for delta inrange(256): p_guess = [bxor(tables[0][y], delta) for y inrange(256)] iflen(set(p_guess)) != 256: continue aes_guess = subword_key_schedule(K0, p_guess) rk10 = matrix2text(aes_guess.round_keys[40:]).to_bytes(16) if rk10[0] != delta: continue good = True for j inrange(16): for y inrange(256): if bxor(p_guess[y], rk10[j]) != tables[j][y]: good = False break ifnot good: break if good: best = (delta, p_guess, aes_guess, rk10) break
if best isNone: raise ValueError("no Delta satisfied the full consistency checks")
from hashlib import md5 from operator import xor as bit_xor from fpylll import BKZ, IntegerMatrix from z3 import BitVec, BitVecVal, SolverFor, ULT, ZeroExt from pwn import * import time
defrotation_candidates(out): return [r for r inrange(256) if (rol(out, r, 256) >> 128) == 0]
defenumerate_low_trajectories(a0, outs): a_low = a0 & SLICE_MASK rot_sets = [rotation_candidates(out) for out in outs] x_sets = [ sorted({(rot << 6) | lo for rot in rots for lo inrange(64)}) for rots in rot_sets ] x_membership = [set(values) for values in x_sets]
layer = {} for x0 in x_sets[0]: ax = (a_low * x0) & SLICE_MASK for x1 in x_sets[1]: u = (x1 - ax) & SLICE_MASK bucket = layer.setdefault(u, {}) bucket.setdefault(x1, x0)
parents = [layer] for i inrange(1, len(outs) - 1): next_layer = {} valid = x_membership[i + 1] for u, current in layer.items(): nxt = {} for x_i in current.keys(): x_next = (a_low * x_i + u) & SLICE_MASK if x_next in valid and x_next notin nxt: nxt[x_next] = x_i if nxt: next_layer[u] = nxt ifnot next_layer: return parents.append(next_layer) layer = next_layer
seen = set() for u, current in layer.items(): for x_last in current.keys(): xs = [0] * len(outs) xs[-1] = x_last for i inrange(len(outs) - 2, 0, -1): xs[i] = parents[i][u][xs[i + 1]] xs[0] = parents[0][u][xs[1]] key = (u, tuple(xs)) if key in seen: continue seen.add(key) yield u, xs
defrecover_geometric_sequence(a0, obs): scale = 1 << QUOT_BITS embed = 1 << QUOT_BITS for dim in (10, 11, 12): basis = IntegerMatrix(dim, dim) power = 1 for j inrange(dim): basis[0, j] = power power = (power * a0) & LOW_MASK for i inrange(1, dim): basis[i, i] = 1 << 128
target = [int(value) * scale for value in obs[:dim]] embedded = IntegerMatrix(dim + 1, dim + 1) for i inrange(dim): for j inrange(dim): embedded[i, j] = basis[i, j] for j inrange(dim): embedded[dim, j] = target[j] embedded[dim, dim] = embed BKZ.reduction(embedded, BKZ.Param(block_size=dim + 1))
candidates = [] for i inrange(dim + 1): row = [embedded[i, j] for j inrange(dim + 1)] candidates.append(row) candidates.append([-x for x in row])
for row insorted(candidates, key=lambda values: sum(x * x for x in values)): ifabs(row[-1]) != embed: continue if row[-1] == -embed: lattice_vec = [(target[j] + row[j]) & LOW_MASK for j inrange(dim)] else: lattice_vec = [(target[j] - row[j]) & LOW_MASK for j inrange(dim)]
g0 = lattice_vec[0] & LOW_MASK sequence = [] cur = g0 ok = True for value in obs: noise = (value - (cur >> QUOT_BITS)) & SLICE_MASK if noise notin (0, 1, 2): ok = False break sequence.append(cur) cur = (cur * a0) & LOW_MASK if ok: return sequence returnNone
if solver.check().r != 1: returnNone return solver.model()[offset].as_long()
defiter_congruence_solutions(a, rhs, bits=256): mod = 1 << bits a %= mod rhs %= mod if a == 0: if rhs == 0: raise ValueError("degenerate multiplier: infinitely many solutions") return nu = (a & -a).bit_length() - 1 g = 1 << nu if rhs & (g - 1): return if g > (1 << 20): raise ValueError("too many congruence solutions") a_reduced = a >> nu rhs_reduced = rhs >> nu mod_reduced = mod >> nu inv = pow(a_reduced, -1, mod_reduced) base = (rhs_reduced * inv) % mod_reduced for k inrange(g): yield base + k * mod_reduced
defsolve_instance(a_raw, outs, seed_hash): a = a_raw & MASK if a == 0: returnNone a0 = a & LOW_MASK a1 = (a >> 128) & LOW_MASK
for u, xs in enumerate_low_trajectories(a0, outs): rotations = [x >> 6for x in xs] zs = [rol(out, rot, 256) for out, rot inzip(outs, rotations)] ifany(z >> 128for z in zs): continue
h_low = [bit_xor(z & SLICE_MASK, x) for z, x inzip(zs, xs)] b_low14 = [ (h_low[i + 1] - a0 * h_low[i] - a1 * xs[i]) & SLICE_MASK for i inrange(len(xs) - 1) ] q = [ (a0 * xs[i] + u - xs[i + 1]) >> 14 for i inrange(len(xs) - 1) ] approx = [ (b_low14[i + 1] - b_low14[i]) & SLICE_MASK for i inrange(len(b_low14) - 1) ] delta_known = [ (q[i + 2] - q[i + 1] - a0 * (approx[i] << QUOT_BITS)) & LOW_MASK for i inrange(len(q) - 2) ] k_part = [0] for term in delta_known: k_part.append((a0 * k_part[-1] + term) & LOW_MASK) obs = [ (approx[i] - (k_part[i] >> QUOT_BITS)) & SLICE_MASK for i inrange(len(k_part)) ] geom = recover_geometric_sequence(a0, obs) if geom isNone: continue
d_seq = [(geom[i] + k_part[i]) & LOW_MASK for i inrange(len(geom))] y_offsets = [0] for value in d_seq: y_offsets.append((y_offsets[-1] + (value & (QUOT_MOD - 1))) & (QUOT_MOD - 1))
wrap_thresholds = [Noneif value == 0else QUOT_MOD - value for value in y_offsets] deltas = [(value - b_tail[0]) & SLICE_MASK for value in b_tail] points = {0, QUOT_MOD} for point in carry_thresholds + wrap_thresholds: if point isnotNone: points.add(point) points = sorted(points)
first_threshold = carry_thresholds[0] for left, right inzip(points, points[1:]): if left >= right: continue pivot = left first_carry = 0if first_threshold isNoneelseint(pivot >= first_threshold) ok = True for i inrange(1, len(q_tail)): extra_carry = 0if carry_thresholds[i] isNoneelseint(pivot >= carry_thresholds[i]) wrap = 0if wrap_thresholds[i] isNoneelseint(pivot >= wrap_thresholds[i]) value = (quotients[i] - quotients[0] + extra_carry - first_carry - a0 * wrap) & SLICE_MASK if value != deltas[i]: ok = False break ifnot ok: continue
carries = [ 0if carry_thresholds[i] isNoneelseint(left >= carry_thresholds[i]) for i inrange(len(q_tail)) ] wraps = [ 0if wrap_thresholds[i] isNoneelseint(left >= wrap_thresholds[i]) for i inrange(len(y_offsets)) ] shifted_offsets = [ y_offsets[i] - wraps[i] * QUOT_MOD for i inrange(len(y_offsets)) ] carry_terms = [ quotients[i] + carries[i] - a0 * wraps[i] for i inrange(len(q_tail)) ] base_y = [shifted_offsets[i] + left for i inrange(len(y_offsets))]
offset = solve_interval_offset(a0, a1, xs_tail, zs_tail, carry_terms, base_y, right - left) if offset isNone: continue
y_values = [base + offset for base in base_y] ifany(value < 0or value >= QUOT_MOD for value in y_values): continue lower_states = [ xs_tail[i] + (y_values[i] << 14) for i inrange(len(xs_tail)) ] upper_states = [bit_xor(zs_tail[i], lower_states[i]) for i inrange(len(xs_tail))]
from Crypto.Util.number import * flag = b'******' m = bytes_to_long(flag) bits = 1024 delta = 0.08 gamma = 0.39 delta0 = 0.33
p = getPrime(bits // 2) q = getPrime(bits // 2) N = p * q d = getPrime(int(bits*delta0)) phi = (p-1)*(q-1) e = inverse(d,phi) S = p + q - (p + q) % (2**int(bits * gamma)) c = pow(m,e,N)
""" N = 92365041570462372694496496651667282908316053786471083312533551094859358939662811192309357413068144836081960414672809769129814451275108424713386238306177182140825824252259184919841474891970355752207481543452578432953022195722010812705782306205731767157651271014273754883051030386962308159187190936437331002989 e = 11633089755359155730032854124284730740460545725089199775211869030086463048569466235700655506823303064222805939489197357035944885122664953614035988089509444102297006881388753631007277010431324677648173190960390699105090653811124088765949042560547808833065231166764686483281256406724066581962151811900972309623 c = 49076508879433623834318443639845805924702010367241415781597554940403049101497178045621761451552507006243991929325463399667338925714447188113564536460416310188762062899293650186455723696904179965363708611266517356567118662976228548528309585295570466538477670197066337800061504038617109642090869630694149973251 S = 19240297841264250428793286039359194954582584333143975177275208231751442091402057804865382456405620130960721382582620473853285822817245042321797974264381440 """
import os # clone RSA_CFL/attack.py from https://github.com/MengceZheng/RSA_CFL from attack import approximate_sum_attack from Crypto.Util.number import long_to_bytes N = 92365041570462372694496496651667282908316053786471083312533551094859358939662811192309357413068144836081960414672809769129814451275108424713386238306177182140825824252259184919841474891970355752207481543452578432953022195722010812705782306205731767157651271014273754883051030386962308159187190936437331002989 e = 11633089755359155730032854124284730740460545725089199775211869030086463048569466235700655506823303064222805939489197357035944885122664953614035988089509444102297006881388753631007277010431324677648173190960390699105090653811124088765949042560547808833065231166764686483281256406724066581962151811900972309623 c = 49076508879433623834318443639845805924702010367241415781597554940403049101497178045621761451552507006243991929325463399667338925714447188113564536460416310188762062899293650186455723696904179965363708611266517356567118662976228548528309585295570466538477670197066337800061504038617109642090869630694149973251 S = 19240297841264250428793286039359194954582584333143975177275208231751442091402057804865382456405620130960721382582620473853285822817245042321797974264381440 bits = 1024 delta = 0.08 gamma = 0.39 delta0 = 0.33
solutions = approximate_sum_attack(N, e, S, delta0, gamma, 6, 4, "auto", 10) assert solutions w = solutions[1]
p_plus_q = S + w disc = p_plus_q * p_plus_q - 4 * N assert disc >= 0and disc.is_square() sqrt_disc = disc.sqrt() p = (p_plus_q + sqrt_disc) // 2 q = (p_plus_q - sqrt_disc) // 2 assert p * q == N phi = (p - 1) * (q - 1) d = inverse_mod(e, phi) m = pow(c, d, N) print(f"flag : {long_to_bytes(m)}")
''' INFO:root:Trying approximate_sum_attack with m = 6, t = 4... INFO:root:Generating shifts... INFO:root:Generating the lattice... INFO:root:Reducing the lattice... INFO:root:Reducing a 46 x 46 lattice within 2.108 seconds... INFO:root:Reconstructed 45 non-constant polynomials... INFO:root:Reduced to 10 bivariate polynomials after substituting u = x*y - 1... INFO:root:Found one possible solution: x0 = 23046290722813476038718953853202262665577865587504904916206909233597137226666603418973995697517833379, y0 = 683148815721841766742686899713115888494025265940826866001098672064769240843667757982350507024623048793200922731436972626, u0 = 15744046214071294834103021682055868882274013700814065864022523774455730099651030460684829899987392033884802839357978059376159523387941398725360575425933917031997836744748181214612690925779888634778239676368707382452083253 flag : b'SUCTF{congratulation_you_know_small_d_with_hint_factor}' '''
defrecv_until_hint(io, total_timeout): end = time.time() + total_timeout chunks = [] while time.time() < end: chunk = recv_some(io, RECV_TIMEOUT) if chunk: chunks.append(chunk) data = b"".join(chunks) if HINT_RE.search(data): return data else: time.sleep(DELAY) returnb"".join(chunks)
defcollect_hints(io): hints = [] for idx inrange(HINT_COUNT): io.sendline(b"2") timeout = FIRST_HINT_TIMEOUT if idx == 0else HINT_TIMEOUT data = recv_until_hint(io, timeout) match = HINT_RE.search(data) ifnotmatch: raise RuntimeError(f"failed to parse hint #{idx} from: {data!r}") hints.append(int(match.group(1))) if (idx + 1) % 16 == 0or idx + 1 == HINT_COUNT: print(f"[+] collected {idx + 1}/{HINT_COUNT} hints") time.sleep(DELAY) return hints
defbuild_unknown_m_lattice(hints): q = Integer(1) << ALPHA L = Matrix(ZZ, R + T, R + T) for i inrange(T): L[i, i] = q for i inrange(R): window = hints[i : i + T] for j inrange(T): L[T + i, j] = Integer(window[j]) L[T + i, T + i] = 1 return L
defcoeffs_to_poly(coeffs): ring = ZZ["x"] x = ring.gen() returnsum(ZZ(c) * x**i for i, c inenumerate(coeffs))
defextract_annihilating_rows(B): rows = [] for idx inrange(min(R, B.nrows())): coeffs = tuple(int(c) for c inlist(B.row(idx)[T:])) rows.append({"index": idx, "coeffs": coeffs, "poly": coeffs_to_poly(coeffs)}) return rows
defrecover_modulus(rows): polys = [row["poly"] for row in rows] g = ZZ(0) for i inrange(1, len(polys)): for j inrange(i): rr = polys[i].resultant(polys[j]) if rr == 0: continue g = gcd(g, abs(ZZ(rr))) if g != 0and g.nbits() == (ALPHA + BETA) * ORDER: break if g == 0: raise RuntimeError("failed to recover gcd of resultants")
approx = Integer((RR(g) ** (RR(1) / ORDER)).round()) bound = 1 << BETA for delta inrange(bound + 1): for cand in (approx - delta, approx + delta): cand = Integer(cand) if cand <= 1or g % cand != 0: continue tmp = Integer(g) exp = 0 while tmp % cand == 0: tmp //= cand exp += 1 if exp >= ORDER: print(f"[+] recovered modulus = {cand}") returnint(cand) raise RuntimeError(f"failed to refine exact modulus near {approx}")
defrecover_coeffs(rows, modulus): field = GF(modulus) ring = PolynomialRing(field, "x") x = ring.gen() polys = [] for row in rows: poly = sum(field(c) * x**i for i, c inenumerate(row["coeffs"])) if poly.degree() >= ORDER: polys.append((int(row["index"]), poly)) iflen(polys) >= 12: break
best_degree = -1 best_pair = None for i inrange(1, len(polys)): for j inrange(i): gg = polys[i][1].gcd(polys[j][1]).monic() degree = int(gg.degree()) if degree > best_degree: best_degree = degree best_pair = (polys[j][0], polys[i][0]) if degree == ORDER: coeffs = [int((-gg[k]) % modulus) for k inrange(ORDER)] print(f"[+] recovered recurrence from rows {polys[j][0]} and {polys[i][0]}") return coeffs raise RuntimeError( f"failed to recover degree-{ORDER} polynomial, best degree was {best_degree} from pair {best_pair}" )
for block_size in STATE_BKZ_BLOCKS: B = L.BKZ(block_size=block_size) for row_idx inrange(B.nrows()): row = list(B.row(row_idx)) if row[0] == half: low = [int((half - row[i + 1]) % two_beta) for i inrange(ORDER)] elif row[0] == -half: low = [int((row[i + 1] + half) % two_beta) for i inrange(ORDER)] else: continue
tried_rows.append(int(row_idx)) observed = [hints[i] * two_beta + low[i] for i inrange(ORDER)] state = list(observed) ok = True for idx inrange(ORDER, TRUNC_COUNT): nxt = forward_step(state, coeffs, modulus) if nxt >> BETA != hints[idx]: ok = False break state = state[1:] + [nxt] if ok: print(f"[+] recovered observed state from BKZ-{block_size} row {row_idx}") return observed
raise RuntimeError(f"failed to validate any state candidate, tried rows {tried_rows}")
defstep_back(state, coeffs, modulus, steps): st = list(state) inv_c0 = int(inverse_mod(coeffs[0], modulus)) for _ inrange(steps): prev = (st[-1] - sum(coeffs[j] * st[j - 1] for j inrange(1, ORDER))) * inv_c0 % modulus st = [prev] + st[:-1] return st
from Crypto.Util.number import * from Crypto.Cipher import AES from Crypto.Util.Padding import * from hashlib import sha256 from secret import flag from random import randint import os
p = 5326738796327623094747867617954605554069371494832722337612446642054009560026576537626892113026381253624626941643949444792662881241621373288942880288065659
F = GF(p)
pl = [x for x in prime_range(3, 374) + [587]] pvA = [randint(-5, 5) for _ in pl] pvB = [randint(-5, 5) for _ in pl]
assertlen(pl) == len(pvA) == len(pvB)
defcal(A, sk): E = EllipticCurve(F, [0, A, 0, 1, 0]) for sgn in [1, -1]: for e, ell inzip(sk, pl): for i inrange(sgn * e): whilenot (P := (p + 1) // ell * E.random_element()) or ell * P != 0: pass E = E.isogeny_codomain(P) E = E.quadratic_twist() return E.montgomery_model().a2()
menu = """[1] Get public key [2] Get gift [3] Get flag [4] Exit >>> """
defmain(): whileTrue: try: op = input(menu) if op == "1": pkA = cal(0, pvA) pkB = cal(0, pvB) print(f"pkA: {pkA}") print(f"pkB: {pkB}") elif op == "2": pkA = int(input("pkA >>> ")) pkB = int(input("pkB >>> ")) A = cal(pkA, pvB) B = cal(pkB, pvA) if A != B: print("Illegal public key!") print(f"Gift : {int(A) >> 200}") elif op == "3": key = sha256(str(cal(cal(0, pvB), pvA)).encode()).digest() print(f"Here is your flag: {AES.new(key, AES.MODE_ECB).encrypt(pad(flag, 16)).hex()}") elif op == "4": print("Bye~") break else: print("Invalid option.") except: print("Something wrong...") break
defcal(A, sk): E = EllipticCurve(F, [0, A, 0, 1, 0]) for sgn in [1, -1]: for e, ell inzip(sk, pl): for i inrange(sgn * e): whilenot (P := (p + 1) // ell * E.random_element()) or ell * P != 0: pass E = E.isogeny_codomain(P) E = E.quadratic_twist() return E.montgomery_model().a2()
from copy import deepcopy import logging import os import re import tempfile import time from hashlib import sha256 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad from fpylll import IntegerMatrix from pwn import *
context.log_level = "critical"
P = ZZ(5326738796327623094747867617954605554069371494832722337612446642054009560026576537626892113026381253624626941643949444792662881241621373288942880288065659) F = GF(P) UNKNOWN_BITS = 200 BOUND = 2 ** 200
defextract_match(pattern, text): match = re.search(pattern, text) ifmatchisNone: raise ValueError(f"Could not parse `{pattern}` from output:\n{text}") returnmatch.group(1)
from Crypto.Util.number import * from Crypto.Util.Padding import * from random import randint, choice, choices from hashlib import sha3_512 from base64 import b64encode, b64decode from secret import flag import numpy as np import json import os # import pty
H = lambda x: [int(y, 16) for y in [sha3_512(x).hexdigest()[i:i+2] for i inrange(0, 128, 2)]] alphabet = "".join([chr(i) for i inrange(33, 127)])
classPoint: def__init__(self, x): ifisinstance(x, float): raise ValueError("...") whilenotisinstance(x, int): x = x.x self.x = x
def__add__(self, other): ifisinstance(other, int): returnself.x + other return Point(min(self.x, other.x))
def__radd__(self, other): ifisinstance(other, int): returnself.x + other return Point(min(self.x, other.x))
classBlock: def__init__(self, n, m, data=None): self.n = n self.m = m if data and (len(data) != n orlen(data[0]) != m): raise ValueError("...") if data: ifisinstance(data, Point): self.data = [[Point(data[i][j].x) for j inrange(m)] for i inrange(n)] else: self.data = [[Point(data[i][j]) for j inrange(m)] for i inrange(n)] else: self.data = [[Point(randint(0, 255)) for _ inrange(m)] for _ inrange(n)]
def__add__(self, other): return Block(self.n, self.m, [[self.data[i][j] + other.data[i][j] for j inrange(self.m)] for i inrange(self.n)])
def__mul__(self, other): assertself.m == other.n, "😭" res = [[Point(511) for _ inrange(other.m)] for _ inrange(self.n)] for i inrange(self.n): for j inrange(other.m): for k inrange(self.m): res[i][j] = res[i][j] + (self.data[i][k] * other.data[k][j])
return Block(self.n, other.m, res)
def__eq__(self, other): res = True for i inrange(self.n): for j inrange(self.m): res = res andself.data[i][j] == other.data[i][j] return res
def__getitem__(self, item): returnself.data[item]
def__repr__(self): returnf"{self.data}"
deflegitimacy(self, lb, rb): for i inrange(self.n): for j inrange(self.m): ifnot (lb <= int(self.data[i][j].x) <= rb): returnFalse returnTrue
classRestaurant: def__init__(self, m, n, k): self.m, self.n, self.k = m, n, k self.chef = Block(m, k) self.cooker = Block(k, n) self.fork = self.chef * self.cooker
defcook(self, msg): ifisinstance(msg, str): msg = msg.encode() tmp = H(msg) M = Block(self.n, self.m, [[tmp[i * self.m + j] for j inrange(self.m)] for i inrange(self.n)]) whileTrue: U = Block(self.n, self.k) V = Block(self.k, self.m) P = self.chef * V R = U * self.cooker S = U * V A = (M * self.chef) + U B = (self.cooker * M) + V if A != U and B != V: break return A, B, P, R, S
defeat(self, msg, A, B, P, R, S): ifisinstance(msg, str): msg = msg.encode() tmp = H(msg) M = Block(self.n, self.m, [[tmp[i * self.m + j] for j inrange(self.m)] for i inrange(self.n)]) Z = (M * self.fork * M) + (M * P) + (R * M) + S W = A * B legal = A.legitimacy(0, 256) and B.legitimacy(0, 256) and P.legitimacy(0, 256) and R.legitimacy(0, 256) and S.legitimacy(0, 256) return W == Z and W != S and legal
menu = """Do something... [1] Say to the waiter: "Please give me some food." [2] Say to the waiter: "Please give me the FLAG!" [3] Check out What do you want to do? >>> """
havefork = False try: whileTrue: op = int(input(menu)) if op == 1: iflen(table) == 2: print("You're full and don't want to order more...") continue foodname = choice(foodlist) while foodname in table: foodname = choice(foodlist) print(f'The waiter says: "Here is your {foodname}!"') table.append(foodname) A, B, P, R, S = SU_Restaurant.cook(foodname) print(f'A = {A}\nB = {B}\nP = {P}\nR = {R}\nS = {S}')
elif op == 2: Fo0dN4mE = "".join(choices(alphabet, k=36)) print(f'The waiter says: "Please make {Fo0dN4mE} for me!"') res = json.loads(input(">>> ")) r1 = np.linalg.matrix_rank(np.array(res["A"])) r2 = np.linalg.matrix_rank(np.array(res["B"])) r3 = np.linalg.matrix_rank(np.array(res["P"])) r4 = np.linalg.matrix_rank(np.array(res["R"])) r5 = np.linalg.matrix_rank(np.array(res["S"]))
A = Block(8, 7, res["A"]) B = Block(7, 8, res["B"]) P = Block(8, 8, res["P"]) R = Block(8, 8, res["R"]) S = Block(8, 8, res["S"]) if SU_Restaurant.eat(Fo0dN4mE, A, B, P, R, S): print(f'The waiter says: "Here is the FLAG: {flag}"') else: print('The waiter says: "This is not what I wanted!"') exit(0)
elif op == 3: print('The waiter says: "Welcome to our restaurant next time!"') break else: print("Invalid option!") except: print("Something wrong...") exit(0)
import ast import json import random import re import sys
import numpy as np from pwn import * from z3 import Int, Or, Solver, sat
from main import H
context.log_level = "info"
MENU_PROMPT = b">>> " TARGET_RE = re.compile(r'Please make (?P<msg>.+?) for me!') FOOD_RE = re.compile(r"Here is your (?P<food>.+?)!") MATRIX_RE = re.compile(r"([ABPRS]) = (\[\[.*?\]\])", re.S)
defhash_matrix(msg): ifisinstance(msg, str): msg = msg.encode() tmp = H(msg) return [[tmp[i * 8 + j] for j inrange(8)] for i inrange(8)]
deftrop_add(a, b): return [[min(a[i][j], b[i][j]) for j inrange(len(a[0]))] for i inrange(len(a))]
deftrop_mul(a, b): rows = len(a) mid = len(b) cols = len(b[0]) out = [[0] * cols for _ inrange(rows)] for i inrange(rows): for j inrange(cols): out[i][j] = min(a[i][k] + b[k][j] for k inrange(mid)) return out
defadd_min_eq(solver, c, terms): for term in terms: solver.add(c <= term) solver.add(Or(*[c == term for term in terms]))
defparse_signature_block(blob): text = blob.decode() food_match = FOOD_RE.search(text) ifnot food_match: raise ValueError(f"failed to parse food name from:\n{text}") mats = {} for name, matrix_text in MATRIX_RE.findall(text): mats[name] = ast.literal_eval(matrix_text) ifset(mats) != {"A", "B", "P", "R", "S"}: raise ValueError(f"failed to parse all matrices from:\n{text}") return food_match.group("food"), mats
x = [[Int(f"x_{i}_{j}") for j inrange(7)] for i inrange(8)] y = [[Int(f"y_{i}_{j}") for j inrange(8)] for i inrange(7)] t = [[Int(f"t_{i}_{j}") for j inrange(8)] for i inrange(8)] u_all = [] v_all = []
for r inrange(len(signatures)): u_all.append([[Int(f"u_{r}_{i}_{j}") for j inrange(7)] for i inrange(8)]) v_all.append([[Int(f"v_{r}_{i}_{j}") for j inrange(8)] for i inrange(7)])
for arr in [x, y] + u_all + v_all: for row in arr: for var in row: solver.add(var >= 0, var <= 255) for row in t: for var in row: solver.add(var >= 0, var <= 510)
for i inrange(8): for j inrange(8): add_min_eq(solver, t[i][j], [x[i][k] + y[k][j] for k inrange(7)])
for r, sig inenumerate(signatures): m = sig["M"] a = sig["A"] b = sig["B"] p = sig["P"] rr = sig["R"] s = sig["S"] w = sig["W"] u = u_all[r] v = v_all[r]
for i inrange(8): for j inrange(7): add_min_eq(solver, a[i][j], [u[i][j]] + [m[i][k] + x[k][j] for k inrange(8)])
for i inrange(7): for j inrange(8): add_min_eq(solver, b[i][j], [v[i][j]] + [y[i][k] + m[k][j] for k inrange(8)])
for i inrange(8): for j inrange(8): add_min_eq(solver, p[i][j], [x[i][k] + v[k][j] for k inrange(7)]) add_min_eq(solver, rr[i][j], [u[i][k] + y[k][j] for k inrange(7)]) add_min_eq(solver, s[i][j], [u[i][k] + v[k][j] for k inrange(7)])
mtm_terms = [m[i][a0] + t[a0][b0] + m[b0][j] for a0 inrange(8) for b0 inrange(8)] mp_terms = [m[i][k] + p[k][j] for k inrange(8)] rm_terms = [rr[i][k] + m[k][j] for k inrange(8)] add_min_eq(solver, w[i][j], mtm_terms + mp_terms + rm_terms + [s[i][j]])
if solver.check() != sat: raise RuntimeError("SMT solver did not find a satisfying equivalent key")
model = solver.model() x_val = [[model[x[i][j]].as_long() for j inrange(7)] for i inrange(8)] y_val = [[model[y[i][j]].as_long() for j inrange(8)] for i inrange(7)] t_val = [[model[t[i][j]].as_long() for j inrange(8)] for i inrange(8)] return x_val, y_val, t_val
defforge_payload(target, x, y, max_attempts=2000): m = hash_matrix(target) for _ inrange(max_attempts): u = [[random.randint(0, 255) for _ inrange(7)] for _ inrange(8)] v = [[random.randint(0, 255) for _ inrange(8)] for _ inrange(7)] a = trop_add(trop_mul(m, x), u) b = trop_add(trop_mul(y, m), v) p = trop_mul(x, v) r = trop_mul(u, y) s = trop_mul(u, v)
ranks = [np.linalg.matrix_rank(np.array(mat)) for mat in [a, b, p, r, s]] if ranks[0] >= 7and ranks[1] >= 7and ranks[2] >= 8and ranks[3] >= 8and ranks[4] >= 8: return { "A": a, "B": b, "P": p, "R": r, "S": s, } raise RuntimeError("failed to sample a payload that passes the rank checks")
defrequest_target(io): io.sendline(b"2") blob = io.recvuntil(MENU_PROMPT, drop=False) text = blob.decode() match = TARGET_RE.search(text) ifnotmatch: raise ValueError(f"failed to parse target message from:\n{text}") returnmatch.group("msg"), blob
defrun_once(attempt): # io = process(["python", "main.py"]) io = remote("101.245.107.149", 10020) try: signatures = collect_two_signatures(io) foods = ", ".join(sig["food"] for sig in signatures) print(f"[*] attempt {attempt}: signatures = {foods}")
x, y, t = recover_equivalent_key(signatures) print(f"[*] attempt {attempt}: recovered model sample x[0][0]={x[0][0]}, y[0][0]={y[0][0]}, t[0][0]={t[0][0]}")
payload = forge_payload(target, x, y) io.sendline(json.dumps(payload).encode()) result = io.recvall(timeout=2).decode(errors="replace") success = "SUCTF{"in result return success, result finally: io.close()
defmain(): max_attempts = 20 for attempt inrange(1, max_attempts + 1): success, result = run_once(attempt) print(result) if success: return raise SystemExit(f"failed after {max_attempts} attempts")
if __name__ == "__main__": main()
''' The waiter says: "This is not what I wanted!" Something wrong... [x] Opening connection to 101.245.107.149 on port 10020 [x] Opening connection to 101.245.107.149 on port 10020: Trying 101.245.107.149 [+] Opening connection to 101.245.107.149 on port 10020: Done [*] attempt 2: signatures = Red Rice Rolls, Cha siu bao [*] attempt 2: recovered model sample x[0][0]=147, y[0][0]=155, t[0][0]=28 [*] attempt 2: target = 1g-$bM:s6fgsDqQ7.+b#F,%+sNOx)*uof{"& [x] Receiving all data [x] Receiving all data: 0B [x] Receiving all data: 246B [+] Receiving all data: Done (246B) [*] Closed connection to 101.245.107.149 port 10020 The waiter says: "Here is the FLAG: b'SUCTF{W3lc0m3_t0_SU_R3stAur4nt_n3Xt_t1me!:-)}'" Do something... [1] Say to the waiter: "Please give me some food." [2] Say to the waiter: "Please give me the FLAG!" [3] Check out What do you want to do? '''