Skip to content

Instantly share code, notes, and snippets.

@Connor-McCartney
Last active March 3, 2024 15:33
Show Gist options
  • Save Connor-McCartney/31387d1b32cf0d5f2d06f86f2e1c112c to your computer and use it in GitHub Desktop.
Save Connor-McCartney/31387d1b32cf0d5f2d06f86f2e1c112c to your computer and use it in GitHub Desktop.
from Crypto.Util.number import *
from subprocess import check_output, CalledProcessError
from re import findall
import itertools
from tqdm import tqdm
def flatter(M):
try:
z = "[[" + "]\n[".join(" ".join(map(str, row)) for row in M) + "]]"
ret = check_output(["flatter"], input=z.encode())
return matrix(M.nrows(), M.ncols(), map(int, findall(b"-?\\d+", ret)))
except:
return M.LLL()
RRh = RealField(5000)
def solve_root_jacobian_newton_internal(pollst, startpnt, maxiternum=500):
# NOTE: Newton method's complexity is larger than BFGS, but for small variables Newton method converges soon.
pollst_Q = Sequence(pollst, pollst[0].parent().change_ring(QQ))
vars_pol = pollst_Q[0].parent().gens()
jac = jacobian(pollst_Q, vars_pol)
if all([ele == 0 for ele in startpnt]):
# just for prepnt != pnt
prepnt = {vars_pol[i]: 1 for i in range(len(vars_pol))}
else:
prepnt = {vars_pol[i]: 0 for i in range(len(vars_pol))}
pnt = {vars_pol[i]: startpnt[i] for i in range(len(vars_pol))}
iternum = 0
while True:
if iternum >= maxiternum:
return None
evalpollst = [(pollst_Q[i].subs(pnt)) for i in range(len(pollst_Q))]
if all([int(ele) == 0 for ele in evalpollst]):
break
jac_eval = jac.subs(pnt)
evalpolvec = vector(QQ, len(evalpollst), evalpollst)
try:
pnt_diff_vec = jac_eval.solve_right(evalpolvec)
except:
return None
prepnt = {key:value for key,value in prepnt.items()}
pnt = {vars_pol[i]: int(pnt[vars_pol[i]] - pnt_diff_vec[i]) for i in range(len(pollst_Q))}
if all([prepnt[vars_pol[i]] == pnt[vars_pol[i]] for i in range(len(vars_pol))]):
return None
prepnt = {key:value for key,value in pnt.items()}
iternum += 1
return [int(pnt[vars_pol[i]]) for i in range(len(vars_pol))]
def solve_root_jacobian_newton(pollst, bounds):
vars_pol = pollst[0].parent().gens()
# not applicable to non-determined system
if len(vars_pol) > len(pollst):
return []
# pollst is not always algebraically independent,
# so just randomly choose wishing to obtain an algebraically independent set
for random_subset in tqdm(Combinations(pollst, k=len(vars_pol))):
for signs in itertools.product([1, -1], repeat=len(vars_pol)):
startpnt = [signs[i] * bounds[i] for i in range(len(vars_pol))]
result = solve_root_jacobian_newton_internal(random_subset, startpnt)
# filter too much small solution
if result is not None:
if all([abs(ele) < 2**16 for ele in result]):
continue
return [result]
def gen_set_leading_monomials(basepoly):
if basepoly.is_constant():
return [basepoly.parent()(1)]
lmset = [basepoly.parent()(1)]
for monomial in basepoly.monomials():
newlmfound = True
newlmset = []
for lmsetele in lmset:
if monomial % lmsetele != 0:
newlmset.append(lmsetele)
if lmsetele % monomial == 0:
newlmfound = False
if newlmfound:
newlmset.append(monomial)
lmset = newlmset[:]
return lmset
def generate_M_with_ExtendedStrategy(basepoly, lm, t, d):
basepoly_vars = basepoly.parent().gens()
n = len(basepoly_vars)
M = {}
basepoly_pow_monos = (basepoly ** t).monomials()
for k in range(t+1):
M[k] = set()
basepoly_powk_monos = (basepoly ** (t - k)).monomials()
for monos in basepoly_pow_monos:
if monos // (lm ** k) in basepoly_powk_monos:
for extra in itertools.product(range(d), repeat=n):
g = monos * prod([v ** i for v, i in zip(basepoly_vars, extra)])
M[k].add(g)
M[t+1] = set()
return M
def shiftpoly(basepoly, baseidx, Nidx, varsidx_lst):
N = basepoly.parent().characteristic()
basepoly_ZZ = basepoly.change_ring(ZZ)
vars_ZZ = basepoly_ZZ.parent().gens()
if len(vars_ZZ) != len(varsidx_lst):
raise ValueError("varsidx_lst len is invalid (on shiftpoly)")
return (basepoly_ZZ ** baseidx) * (N ** Nidx) * prod([v ** i for v, i in zip(vars_ZZ, varsidx_lst)])
def monomialset(fis):
m_set = set()
for fi in fis:
m_set = m_set.union(set(fi.monomials()))
return m_set
def genmatrix_from_shiftpolys(shiftpolys, bounds):
m_lst = list(monomialset(shiftpolys))
vars_ZZ = shiftpolys[0].parent().gens()
if len(vars_ZZ) != len(bounds):
raise ValueError("bounds len is invalid (on genmatrix_from_shiftpolys)")
matele = []
for sftpol in shiftpolys:
sftpol_sub_bound = sftpol.subs({vars_ZZ[i]: vars_ZZ[i]*bounds[i] for i in range(len(vars_ZZ))})
matele += [sftpol_sub_bound.monomial_coefficient(m_lst[i]) for i in range(len(m_lst))]
mat = matrix(ZZ, len(matele)//len(m_lst), len(m_lst), matele)
return mat, m_lst
def filter_LLLresult_coppersmith(basepoly, t, m_lst, lll, bounds, beta=1.0):
vars_ZZ = m_lst[0].parent().gens()
N = basepoly.parent().characteristic()
howgrave_bound = (RRh(N)**RRh(beta))**RRh(t)
if len(m_lst) != lll.ncols():
raise ValueError("lll or trans result is invalid (on filter_LLLresult_coppersmith)")
# use vector (not use matrix norm, but vector norm)
lll_vec = lll.rows()
m_lst_bound = [m_lstele.subs({vars_ZZ[i]: bounds[i] for i in range(len(vars_ZZ))}) for m_lstele in m_lst]
result = []
for lll_vecele in lll_vec:
if all([int(lll_vecele_ele) == 0 for lll_vecele_ele in lll_vecele]):
continue
lll_l1norm = lll_vecele.norm(p=1)
if lll_l1norm >= howgrave_bound:
continue
howgrave_ratio = int(((lll_l1norm/howgrave_bound)*(10**15))*(0.1**15))
pol = 0
for j, m_lstele_bound in enumerate(m_lst_bound):
#assert int(lll_vecele[j]) % int(m_lstele_bound) == 0
pol += (int(lll_vecele[j]) // int(m_lstele_bound)) * m_lst[j]
result.append(pol)
return result
### multivariate coppersmith with some heuristic (jochemsz-may)
def coppersmith_multivariate_heuristic_core(basepoly, bounds, t, d, lm, maxmatsize=100):
print("trying param: t=%d, d=%d, lm=%s" % (t, d, str(lm)))
basepoly_vars = basepoly.parent().gens()
n = len(basepoly_vars)
basepoly_i = basepoly * (1 / basepoly.monomial_coefficient(lm))
M = generate_M_with_ExtendedStrategy(basepoly_i, lm, t, d)
shiftpolys = []
for k in range(t+1):
for mono in M[k] - M[k+1]:
curmono = (mono // (lm ** k))
xi_idx = curmono.exponents()[0]
shiftpolys.append(shiftpoly(basepoly_i, k, t - k, xi_idx))
mat, m_lst = genmatrix_from_shiftpolys(shiftpolys, bounds)
if mat.ncols() > maxmatsize:
print("maxmatsize exceeded: %d" % mat.ncols())
return []
print('LLL start...')
try:
lll = flatter(mat)
except CalledProcessError:
print('flatter crashed')
return []
print('LLL done')
result = filter_LLLresult_coppersmith(basepoly, t, m_lst, lll, bounds)
return result
def coppersmith_multivariate_heuristic(basepoly, bounds, maxd=8):
# auto-tries different t and d
N = basepoly.parent().characteristic()
basepoly_vars = basepoly.parent().gens()
n = len(basepoly_vars)
if n == 1:
raise ValueError("one variable poly")
lms = gen_set_leading_monomials(basepoly)
basepoly_ZZ_vars = basepoly.change_ring(ZZ).parent().gens()
t = 2
curfoundpols = []
while True:
d0 = t
for d_diff in range(0, maxd+1):
d = d0 + d_diff
for lm in lms:
foundpols = coppersmith_multivariate_heuristic_core(basepoly, bounds, t, d, lm, maxmatsize=100)
if len(foundpols) == 0:
continue
curfoundpols += foundpols
curfoundpols = list(set(curfoundpols))
sol = solve_root_jacobian_newton(curfoundpols, bounds)
if sol != [] and sol is not None:
return sol
t += 1
def coppersmith_multivariate_direct(basepoly, bounds, t=2, d=2):
N = basepoly.parent().characteristic()
basepoly_vars = basepoly.parent().gens()
n = len(basepoly_vars)
if n == 1:
raise ValueError("one variable poly")
lms = gen_set_leading_monomials(basepoly)
basepoly_ZZ_vars = basepoly.change_ring(ZZ).parent().gens()
curfoundpols = []
for lm in lms:
foundpols = coppersmith_multivariate_heuristic_core(basepoly, bounds, t, d, lm, maxmatsize=100)
if len(foundpols) == 0:
continue
curfoundpols += foundpols
curfoundpols = list(set(curfoundpols))
sol = solve_root_jacobian_newton(curfoundpols, bounds)
if sol != [] and sol is not None:
return sol
def daisy_bell_bi0sCTF_2024():
n = 13588728652719624755959883276683763133718032506385075564663911572182122683301137849695983901955409352570565954387309667773401321714456342417045969608223003274884588192404087467681912193490842964059556524020070120310323930195454952260589778875740130941386109889075203869687321923491643408665507068588775784988078288075734265698139186318796736818313573197531378070122258446846208696332202140441601055183195303569747035132295102566133393090514109468599210157777972423137199252708312341156832737997619441957665736148319038440282486060886586224131974679312528053652031230440066166198113855072834035367567388441662394921517
c = 7060838742565811829053558838657804279560845154018091084158194272242803343929257245220709122923033772911542382343773476464462744720309804214665483545776864536554160598105614284148492704321209780195710704395654076907393829026429576058565918764797151566768444714762765178980092544794628672937881382544636805227077720169176946129920142293086900071813356620614543192022828873063643117868270870962617888384354361974190741650616048081060091900625145189833527870538922263654770794491259583457490475874562534779132633901804342550348074225239826562480855270209799871618945586788242205776542517623475113537574232969491066289349
p_high = 914008410449727213564879221428424249291351166169082040257173225209300987827116859791069006794049057028194309080727806930559540622366140212043574
u_low = 233711553660002890828408402929574055694919789676036615130193612611783600781851865414087175789069599573385415793271613481055557735270487304894489126945877209821010875514064660591650207399293638328583774864637538897214896592130226433845320032466980448406433179399820207629371214346685408858
q_high = n // (p_high<<545)
PR.<x, y> = PolynomialRing(Zmod(n), 2)
qq = q_high + x
uu = y * 2**955 + u_low
f = uu*qq**2 - qq
bounds = (2**545, 2**(1024-955))
q_low, u_high = coppersmith_multivariate_direct(f, [2**545, 2**68], t=2, d=4)[0]
q = int(qq(x=q_low))
print(long_to_bytes(int(pow(c, pow(65537, -1, (n//q-1)*(q-1)), n))))
def dp_high_ISITDTU_CTF_2022():
n = 12567364021021536069276139071081301321773783503415410122482063162815802632532262852631733566943908930876147793969043836275748336698486250666608690152319164539308799173942970880405365621942480869038031173565836135609803219607046250197218934622063531043191571238635559343630285434743137661162918049427668167596108116123813247574023489647941191092646108484359798178312183857161198552950237016232564837163103701319852345468700518013420435831423503394375188294692875472478236076844354332788143724582596188171429101120375457060113120938123836683660988201443089327738905824392062439034247838606482384586412614406834714072187
e = 302855054306017053454220113135236383717
c = 4891864668386517178603039798367030027018213726546115291869063934737584262406041165900191539273508747711632172112784295237035437771196319634059866827443546543304905951054697225192869131382430968922874630769470296997764149219967748222295126835357440172029624432839432442542014919311928847815297342723030801298870843985791566021870092065045427815279776382037308098892891521540483210118257005467504087917529931331454698510489305696908930175868148922286615019210097019743367080206300230172144203634385318929457785251214794930401419137018353777022634635240368493042317181737723067635048719777029127030494920756862174788399
dp_high = 1528682061327606941204027235121547734644486174542891631806426376137001818312801429494781262718713071077103179054205712581085921624900599327139049785998451580793229375262096344994664540984947702348067124539258177759586538935334513702134177319291812
PR.<x,k> = PolynomialRing(Zmod(n), 2)
f = 1 - k - ((dp_high << 205) + x) * e
roots = coppersmith_multivariate_direct(f, [2**205, 2**128], t=2, d=3)
if roots is None:
print('fail')
return False
x, k = roots[0]
p = gcd(int(f(x, k)), n)
q = int(n) // int(p)
d = pow(e, -1, (p-1)*(q-1))
m = pow(c, int(d), n)
print(long_to_bytes(int(m)))
return True
def tests():
daisy_bell_bi0sCTF_2024()
dp_high_ISITDTU_CTF_2022()
#tests()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment