Last active
June 19, 2024 13:05
-
-
Save maple3142/3f5924162d3def632aff5be791ee42eb to your computer and use it in GitHub Desktop.
vsctf 2024 - salad
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from sage.all import Matrix, vector, Zmod, var, ZZ | |
import random | |
class Random: | |
def __init__(self, modulus): | |
self.r = random.SystemRandom() | |
self.modulus = modulus | |
def next(self): | |
return self.r.randrange(1, self.modulus) | |
class CentralMapping: | |
def __init__(self, o, v, modulus): | |
while True: | |
self.rng = Random(modulus) | |
self.varrng = Random(modulus) | |
self.modulus = modulus | |
self.o = o | |
self.v = v | |
self.a = self.generate_a_b(self.o, self.v, self.o) | |
self.b = self.generate_a_b(self.v, self.v, self.o) | |
self.c = self.generate_c_d(self.o, self.o) | |
self.d = self.generate_c_d(self.v, self.o) | |
self.e = self.generate_e(self.o) | |
if len(list(set(self.e))) > o // 2: | |
break | |
def generate_a_b(self, i, j, k): | |
return [self.generate_c_d(j, k) for _ in range(i)] | |
def generate_c_d(self, j, k): | |
return [self.generate_e(k) for _ in range(j)] | |
def generate_e(self, k): | |
return [self.rng.next() for _ in range(k)] | |
def raw_eval(self, vars): | |
t = [] | |
o = vars[self.v :] | |
v = vars[: self.v] | |
for k in range(self.o): | |
s = 0 | |
for i in range(self.o): | |
for j in range(self.v): | |
s += self.a[i][j][k] * v[j] * o[i] | |
s += self.c[i][k] * o[i] | |
s += self.e[k] | |
for i in range(self.v): | |
for j in range(self.v): | |
s += self.b[i][j][k] * v[i] * v[j] | |
s += self.d[i][k] * v[i] | |
t.append(s) | |
return t | |
def invert(self, t): | |
v = [self.varrng.next() for _ in range(self.v)] | |
o = [var(f"o_{i}") for i in range(self.o)] | |
equations = self.raw_eval(v + o) | |
coeffs = [] | |
target = [] | |
for i in range(self.o): | |
eq = equations[i] | |
subdict = {o[i]: 0 for i in range(self.o)} | |
constant = eq.subs(subdict) | |
coeffs.append( | |
[int(eq.coefficient(o[i])) % self.modulus for i in range(self.o)] | |
) | |
target.append(int(int(t[i]) - constant) % self.modulus) | |
M = Matrix(Zmod(self.modulus), coeffs) | |
A = vector(Zmod(self.modulus), target) | |
o = M.solve_right(A) | |
o = [int(x) for x in o] | |
return v + o | |
def eval(self, vars): | |
return [x % self.modulus for x in self.raw_eval(vars)] | |
def __repr__(self): | |
return ( | |
f"{self.__class__.__name__}(o={self.o}, v={self.v}, modulus={self.modulus})" | |
) | |
class AffineMap: | |
def __init__(self, n, modulus): | |
self.modulus = modulus | |
self.varrng = Random(modulus) | |
self.F = Zmod(modulus) | |
while True: | |
A = Matrix(self.F, self.generate_A(n, n)) | |
if A.is_invertible(): | |
break | |
self.A = A | |
self.Ainv = A.inverse() # precompute the inverse | |
def generate_A(self, m, n): | |
return [self.generate_A_row(m) for _ in range(n)] | |
def generate_A_row(self, n): | |
return [self.varrng.next() for _ in range(n)] | |
def raw_eval(self, x): | |
A_real = list(self.A) | |
data = [] | |
for i in range(len(A_real)): | |
data.append(sum([int(A_real[i][j]) * x[j] for j in range(len(A_real[i]))])) | |
return data | |
def eval(self, x): | |
x = vector(self.F, x) | |
return (self.A * x).list() | |
def invert(self, y): | |
y = vector(self.F, y) | |
return (self.Ainv * y).list() | |
def __repr__(self): | |
return f"{self.__class__.__name__}(n={self.A.nrows()}, modulus={self.modulus})" | |
class Salad: | |
def __init__(self, o, v, modulus): | |
self.modulus = modulus | |
self.o = o | |
self.v = v | |
self.AM1 = AffineMap(o + v, modulus) | |
self.CM = CentralMapping(o, v, modulus) | |
self.AM2 = AffineMap(o, modulus) | |
def eval(self, vars): | |
vars = self.AM1.eval(vars) | |
vars = self.CM.eval(vars) | |
vars = self.AM2.eval(vars) | |
return vars | |
def raw_eval(self, vars): | |
vars = self.AM1.raw_eval(vars) | |
vars = self.CM.raw_eval(vars) | |
vars = self.AM2.raw_eval(vars) | |
return vars | |
def invert(self, vars): | |
vars = self.AM2.invert(vars) | |
vars = self.CM.invert(vars) | |
vars = self.AM1.invert(vars) | |
return vars | |
def __repr__(self): | |
return ( | |
f"{self.__class__.__name__}(o={self.o}, v={self.v}, modulus={self.modulus})" | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from ov import * | |
from sage.all import next_prime, PolynomialRing, GF | |
from hashlib import sha256 | |
from ast import literal_eval | |
o = 4 | |
v = o**2 | |
b = 256 // o | |
q = 2**b | |
q = next_prime(q) | |
S = Salad(o, v, q) | |
def hash(msg): | |
return sha256(msg).hexdigest() | |
def hesh(msg): | |
data = hash(msg) | |
# split into blocks of len b bytes | |
blocks = [data[i : i + (b // 4)] for i in range(0, len(data), (b // 4))] | |
# convert to integers | |
ints = [int(block, 16) for block in blocks] | |
return S.invert(ints) | |
F = GF(q) | |
R = F[",".join([f"v_{i}" for i in range(o + v)])] | |
x = R.gens() | |
pk = S.raw_eval(x) | |
for eq in pk: | |
print(eq % q) | |
TARGET_MSG = b"can i maybe pls have a flag~ >w<" | |
FLAG_SIG = S.eval(hesh(TARGET_MSG)) | |
while True: | |
msg = input("Enter message: ").encode() | |
if msg == TARGET_MSG: | |
sig = literal_eval(input("Enter signature: ")) | |
if S.eval(sig) == FLAG_SIG: | |
print(open("flag.txt").read().strip()) | |
exit(1) | |
else: | |
print("NO") | |
exit(1) | |
h = hesh(msg) | |
c = S.eval(h) | |
print(c) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from sage.all import * | |
from ov import * | |
from sage.all import next_prime, PolynomialRing, GF | |
from hashlib import sha256 | |
from ast import literal_eval | |
from lll_cvp import polynomials_to_matrix | |
import itertools | |
from pwn import process | |
from sage.misc.parser import Parser | |
o = 4 | |
v = o**2 | |
b = 256 // o | |
q = 2**b | |
q = next_prime(q) | |
F = GF(q) | |
R = F[",".join([f"v_{i}" for i in range(o + v)])] | |
xs = R.gens() | |
TARGET_MSG = b"can i maybe pls have a flag~ >w<" | |
FLAG_SIG = [ | |
3656634917524245359, | |
12453171074265785512, | |
12275507555376318998, | |
7425758874069847699, | |
] # just sha256(TARGET_MSG) + some processing | |
io = process(["python", "server.py"]) | |
lcls = {f"v_{i}": xs[i] for i in range(o + v)} | |
parser = Parser(make_var=lcls) | |
pk = [] | |
for _ in range(o): | |
# pk.append(eval(io.recvlineS().strip().replace("^", "**"), {}, lcls)) # I am lazy :) | |
pk.append(parser.parse(io.recvlineS().strip())) | |
S = [ | |
f - t for f, t in zip(pk, FLAG_SIG) | |
] # we want to solve a system that pk(...) = target_sig | |
S_quad = [sum(c * m for c, m in f if m.degree() == 2) for f in S] | |
S_linear = [sum(c * m for c, m in f if m.degree() == 1) for f in S] | |
S_constant = [sum(c * m for c, m in f if m.degree() == 0) for f in S] | |
Amats = [ | |
QuadraticForm(f) for f in S_quad | |
] # note: QuadraticForm can be indexed like a matrix | |
Bmat = Sequence(S_linear).coefficients_monomials()[0] | |
Cvec = vector(S_constant) | |
while True: | |
# find substitutions | |
alpha_0 = [F.random_element() for _ in range(o + v)] | |
alphas = [alpha_0] | |
for T in range(1, o): | |
lineqs = [] | |
for t in range(T): | |
for k in range(o): | |
eq = [0] * (o + v) | |
for i in range(o + v): | |
for j in range(i, o + v): | |
# the formula at page 9 is slightly incorrect, so I have to add the second equation (eq[i] += ...) | |
eq[j] += Amats[k][i, j] * alphas[t][i] | |
eq[i] += Amats[k][j, i] * alphas[t][j] | |
lineqs.append(eq) | |
next_alpha = matrix(lineqs).right_kernel().basis()[0] | |
alphas.append(list(next_alpha)) | |
assert len(alphas) == o | |
assert len(alphas[0]) == o + v | |
for T in range(o, o + v): | |
alphas.append([F.random_element() for _ in range(o + v)]) | |
assert matrix(alphas).rank() == o + v, "unlucky, substitution not invertible" | |
y_to_x = matrix(alphas).T | |
# substitute | |
R2 = F[",".join([f"y_{i}" for i in range(o + v)])] | |
ys = R2.gens() | |
xs_in_y = y_to_x * vector(ys) | |
subs = {x: y for x, y in zip(xs, xs_in_y)} | |
S_prime = [f.subs(subs) for f in S] # rewritten system | |
# extract L from S_prime | |
def extract_L(s, y_t): | |
# we want to avoid monomial division because it is slow... | |
assert y_t.is_monomial() and y_t.degree() == 1 | |
y_t_exp = y_t.exponents()[0] | |
y_t_idx = next(i for i, exp in enumerate(y_t_exp) if exp == 1) | |
ret = 0 | |
for coef, mono in s: | |
mono_exp = mono.exponents()[0] | |
# includes y_t*y_? (? != t) and y_t | |
if mono_exp[y_t_idx] == 1: | |
ret += coef * mono | |
return ret.subs({y_t: 1}) | |
L = [] | |
for s in S_prime: | |
for y in ys[:o]: | |
L.append(extract_L(s, y)) | |
# solve the linear system in vinegar variables (in y) | |
M, monos = polynomials_to_matrix(L) | |
assert monos[-1] == 1 | |
sol = M.right_kernel_matrix()[0] | |
sol = sol / sol[-1] # adjust the last element to 1 | |
sol_vinegar = sol | |
# and result in a linear system in squared oil variables (in y) | |
subs = dict(zip(ys[o:], sol_vinegar)) | |
final_polys = [f.subs(subs) for f in S_prime] | |
# solve it | |
M, monos = polynomials_to_matrix(final_polys) | |
assert monos[-1] == 1 | |
sol = M.right_kernel_matrix()[0] | |
sol = sol / sol[-1] # adjust the last element to 1 | |
sol_oil_squared = sol | |
# if unlucky (due to substitutions), try again | |
if all([s.is_square() for s in sol_oil_squared[:o]]): | |
sol_oil = vector([s.sqrt() for s in sol_oil_squared[:o]]) | |
break | |
print("again") | |
# all the possible roots of squared oil variables are correct solution to the system | |
for sgns in itertools.product((1, -1), repeat=o): | |
sol_ys = vector( | |
list([s * sign for s, sign in zip(sol_oil[:o], sgns)]) + list(sol_vinegar[:v]) | |
) | |
sol_xs = y_to_x * sol_ys # a solution to the OV system | |
subs = dict(zip(xs, sol_xs)) | |
evals = [f.subs(subs) for f in S] | |
print(evals) # all zeros | |
assert [f.subs(subs) for f in pk] == FLAG_SIG | |
io.sendline(TARGET_MSG) | |
io.sendline(repr(tuple(sol_xs)).encode()) | |
io.interactive() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from sage.all import * | |
from ov import * | |
from sage.all import next_prime, PolynomialRing, GF | |
from hashlib import sha256 | |
from ast import literal_eval | |
from lll_cvp import polynomials_to_matrix | |
import itertools | |
o = 4 | |
v = o**2 | |
b = 256 // o | |
q = 2**b | |
q = next_prime(q) | |
salad = Salad(o, v, q) | |
def hash(msg): | |
return sha256(msg).hexdigest() | |
def hesh(msg): | |
data = hash(msg) | |
# split into blocks of len b bytes | |
blocks = [data[i : i + (b // 4)] for i in range(0, len(data), (b // 4))] | |
# convert to integers | |
ints = [int(block, 16) for block in blocks] | |
return salad.invert(ints) | |
TARGET_MSG = b"can i maybe pls have a flag~ >w<" | |
FLAG_SIG = salad.eval(hesh(TARGET_MSG)) # this is actually just sha256(TARGET_MSG) | |
F = GF(q) | |
R = F[",".join([f"v_{i}" for i in range(o + v)])] | |
xs = R.gens() | |
pk = salad.raw_eval(xs) | |
# algorithm from section 7 of http://www.goubin.fr/papers/OILLONG.PDF | |
S = [ | |
f - t for f, t in zip(pk, FLAG_SIG) | |
] # we want to solve a system that pk(...) = target_sig | |
S_quad = [sum(c * m for c, m in f if m.degree() == 2) for f in S] | |
S_linear = [sum(c * m for c, m in f if m.degree() == 1) for f in S] | |
S_constant = [sum(c * m for c, m in f if m.degree() == 0) for f in S] | |
Amats = [ | |
QuadraticForm(f) for f in S_quad | |
] # note: QuadraticForm can be indexed like a matrix | |
Bmat = Sequence(S_linear).coefficients_monomials()[0] | |
Cvec = vector(S_constant) | |
while True: | |
# find substitutions | |
alpha_0 = [F.random_element() for _ in range(o + v)] | |
alphas = [alpha_0] | |
for T in range(1, o): | |
lineqs = [] | |
for t in range(T): | |
for k in range(o): | |
eq = [0] * (o + v) | |
for i in range(o + v): | |
for j in range(i, o + v): | |
# the formula at page 9 is slightly incorrect, so I have to add the second equation (eq[i] += ...) | |
eq[j] += Amats[k][i, j] * alphas[t][i] | |
eq[i] += Amats[k][j, i] * alphas[t][j] | |
lineqs.append(eq) | |
next_alpha = matrix(lineqs).right_kernel().basis()[0] | |
alphas.append(list(next_alpha)) | |
assert len(alphas) == o | |
assert len(alphas[0]) == o + v | |
for T in range(o, o + v): | |
alphas.append([F.random_element() for _ in range(o + v)]) | |
assert matrix(alphas).rank() == o + v, "unlucky, substitution not invertible" | |
y_to_x = matrix(alphas).T | |
# substitute | |
R2 = F[",".join([f"y_{i}" for i in range(o + v)])] | |
ys = R2.gens() | |
# subs = {} | |
# for i, x in enumerate(xs): | |
# t = 0 | |
# for j in range(o + v): | |
# t += alphas[j][i] * ys[j] | |
# subs[x] = t | |
xs_in_y = y_to_x * vector(ys) | |
subs = {x: y for x, y in zip(xs, xs_in_y)} | |
S_prime = [f.subs(subs) for f in S] # rewritten system | |
# extract L from S_prime | |
def extract_L(s, y_t): | |
# we want to avoid monomial division because it is slow... | |
assert y_t.is_monomial() and y_t.degree() == 1 | |
y_t_exp = y_t.exponents()[0] | |
y_t_idx = next(i for i, exp in enumerate(y_t_exp) if exp == 1) | |
ret = 0 | |
for coef, mono in s: | |
mono_exp = mono.exponents()[0] | |
# includes y_t*y_? (? != t) and y_t | |
if mono_exp[y_t_idx] == 1: | |
ret += coef * mono | |
return ret.subs({y_t: 1}) | |
L = [] | |
for s in S_prime: | |
for y in ys[:o]: | |
L.append(extract_L(s, y)) | |
# solve the linear system in vinegar variables (in y) | |
M, monos = polynomials_to_matrix(L) | |
assert monos[-1] == 1 | |
sol = M.right_kernel_matrix()[0] | |
sol = sol / sol[-1] # adjust the last element to 1 | |
sol_vinegar = sol | |
# and result in a linear system in squared oil variables (in y) | |
subs = dict(zip(ys[o:], sol_vinegar)) | |
final_polys = [f.subs(subs) for f in S_prime] | |
# solve it | |
M, monos = polynomials_to_matrix(final_polys) | |
assert monos[-1] == 1 | |
sol = M.right_kernel_matrix()[0] | |
sol = sol / sol[-1] # adjust the last element to 1 | |
sol_oil_squared = sol | |
# if unlucky (due to substitutions), try again | |
if all([s.is_square() for s in sol_oil_squared[:o]]): | |
sol_oil = vector([s.sqrt() for s in sol_oil_squared[:o]]) | |
break | |
print("again") | |
# all the possible roots of squared oil variables are correct solution to the system | |
for sgns in itertools.product((1, -1), repeat=o): | |
sol_ys = vector( | |
list([s * sign for s, sign in zip(sol_oil[:o], sgns)]) + list(sol_vinegar[:v]) | |
) | |
sol_xs = y_to_x * sol_ys # a solution to the OV system | |
subs = dict(zip(xs, sol_xs)) | |
evals = [f.subs(subs) for f in S] | |
print(evals) # all zeros | |
assert salad.eval(list(map(int, sol_xs))) == FLAG_SIG |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment