---
# System prepended metadata

title: 'HTB University CTF 2023: Brains and Bytes'
tags: [writeup]

---

# HTB University CTF 2023: Brains and Bytes

I played HTB University CTF 2023 with my university team @Wanna.W1n and my team solved all crypto challenges. Here is my writeup for two challenges I solved: __Mayday Mayday__ and __Zombie Rolled__

## Mayday Mayday

> After successfully obtaining the research papers by extracting the encryption key, a new obstacle arises. The essential information regarding potential cures, including formulas and test results, is shielded by another layer of encryption. Can you overcome this additional challenge to reveal the final formula, enabling you to initiate the cure creation process?

`source.py`
```python
from Crypto.Util.number import getPrime, GCD, bytes_to_long
from secret import FLAG
from random import randint

class Crypto:
    def __init__(self, bits):
        self.bits = bits
        self.alpha = 1/9
        self.delta = 1/4
        self.known = int(self.bits*self.delta)
    
    def keygen(self):
        while True:
            p, q = [getPrime(self.bits//2) for _ in '__']
            self.e = getPrime(int(self.bits*self.alpha))
            φ = (p-1)*(q-1)
            try:
                dp = pow(self.e, -1, p-1)
                dq = pow(self.e, -1, q-1)
                self.n = p*q
                break
            except:
                pass

        return (self.n, self.e), (dp, dq)

    def encrypt(self, m):
        return pow(m, self.e, self.n)

rsa = Crypto(2048)
_, (dp, dq) = rsa.keygen()

m = bytes_to_long(FLAG)
c = rsa.encrypt(m)

with open('output.txt', 'w') as f:
    f.write(f'N = 0x{rsa.n:x}\n')
    f.write(f'e = 0x{rsa.e:x}\n')
    f.write(f'c = 0x{c:x}\n')
    f.write(f'dp = 0x{(dp >> (rsa.bits//2 - rsa.known)):x}\n')
    f.write(f'dq = 0x{(dq >> (rsa.bits//2 - rsa.known)):x}\n')
```

`output.txt`
```
N = 0x78fb80151a498704541b888b9ca21b9f159a45069b99b04befcb0e0403178dc243a66492771f057b28262332caecc673a2c68fd63e7c850dc534a74c705f865841c0b5af1e0791b8b5cc55ad3b04e25f20dedc15c36db46c328a61f3a10872d47d9426584f410fde4c8c2ebfaccc8d6a6bd1c067e5e8d8f107b56bf86ac06cd8a20661af832019de6e00ae6be24a946fe229476541b04b9a808375739681efd1888e44d41196e396af66f91f992383955f5faef0fc1fc7b5175135ab3ed62867a84843c49bdf83d0497b255e35432b332705cd09f01670815ce167aa35f7a454f8b26b6d6fd9a0006194ad2f8f33160c13c08c81fe8f74e13e84e9cdf6566d2f
e = 0x4b3393c9fe2e50e0c76920e1f34e0c86417f9a9ef8b5a3fa41b381355
c = 0x17f2b5a46e4122ff819807a9d92b6225c483cf93c9804381098ecd6b81f4670e94d8930001b760f1d26bc7aa7dda48c9e12809d20b33fdb4c4dd9190b105b7dab42e932b99aaff54023873381e7387f1b2b18b355d4476b664d44c40413d82a10635fe6e7322543943aed2dcfbe49764b8da70edeb88d6f63ee47f025be5f2f38319611ab74cd5db6f90f60870ecbb57a884f821d873db06aadf0e61ff74cc7d4c8fc1e527dba9b205220c6707f750822c675c530f8ad6956e41ab80911da49c3d6a7d27e93c44ba5968f2f47a9c5a2694c9d6da245ceffe9cab66b6043774f446b1b08ee4739d3cc716b87c8225a84d3c4ea2fdf68143d09f062c880a870554
dp = 0x59a2219560ee56e7c35f310a4d101061aa61e0ae4eae7605eb63784209ad488b4ed161e780811edd61bf593e2d385beccfd255b459382d8a9029943781b540e7
dq = 0x39719131fbfd8afbc972ca005a430d080775bf1a5b3e8b789aba5c5110a31bd155ff13fba1019bb6cb7db887685e34ca7966a891bfad029b55b92c11201559e5

```
In this challenge, we are given 512 MSB of $d_p = d \mod p-1$ and $d_q = d \mod q-1$. This kind of challenge is similar to [a challenge that i solved](/pTtY4gtrQYuiex-cov2hJg) in BauhiniaCTF 2023 (this writeup is written in Vietnamese). I followed this and solved this challenge, and I will explain what i did.

### Finding k, l



Let rewrite $d_p$ and $d_q$:

$$\begin{aligned}d_p &= d_p^{(M)} * 2^{512} + d_p^{(L)} \\ d_q &= d_q^{(M)} * 2^{512} + d_q^{(L)}\end{aligned}$$

Where $d_p^{(M)}, d_q^{(M)}$ is MSBs and $d_p^{(L)}, d_q^{(L)}$ is LSBs. We already have $d_p^{(M)}, d_q^{(M)}$, so our problem is finding LSBs

With $d_p$ and $d_q$, we have:

$$\begin{aligned}ed_p &= k(p-1) + 1 \\ ed_q &= l(p-1) + 1\end{aligned}$$

Rewrite it:

$$\begin{aligned}kp = k - 1 + ed_p \\ lq = l - 1 + ed_q\end{aligned}$$

Multiply $kp$ with $lq$ and we will have:

$$klN = (k - 1)(l - 1) + ed_p(l - 1) + ed_q(k - 1) + e^2d_pd_q \ (1)$$

Now, let

$$A = {2^{1024}e^2d_pd_q \over N}$$

And by Lemma 1 in the paper, we have $\lceil A \rceil = kl$

If we have $kl$, then equation $(1)$ become:

$$k+l = 1 - kl(N - 1) \mod e$$

Now we have $k + l$ and $kl$, and we can use Vieta's formulas over $\mathbb{Z}_e$ to find $k$ and $l$

### Factoring N

In previous part, we have two equations: $ed_p = k(p-1) + 1$ and $d_p = d_p^{(M)} * 2^{512} + d_p^{(L)}$ 

We already know $k$ and $d_p^{(M)}$, so we can combine two equations and get:

$$ed_p^{(L)} + ed_p^{(M)}*2^i + k - 1 = kp$$

This equation yields a polynomial $f(x) = x + a \mod kp$ where $a = (ed_p^{(M)}*2^i + k - 1)(e^-1 \mod kN)$. By using Coppersmith's algorithm, we can get a small root $x_0 = d_p^{(L)}$ of $f(x)$. After that, we can factor $N$ by calculate $gcd(N, f(x_0))$

`solve.sage`
```python
from Crypto.Util.number import *
from sage.all import *

N = ..
e = ..
c = ..
dp = ..
dq = ..

A = (2**1024*e**2*dp*dq)//N + 1

x = PolynomialRing(Zmod(e), 'x').gen()
f = x^2 - (1 - A*(N - 1))*x + A
k, l = [int(c[0]) for c in f.roots()]

x = PolynomialRing(Zmod(k*N), 'x').gen()
a = ((e*dp*2^512 + k - 1) * pow(e, -1, k * N)) % (k * N)
g = x + a
dp_low = int(g.small_roots(X=2**512, beta=0.5)[0])

kp = int(g(dp_low))
p = gcd(kp, N)
q = N // p
assert p * q == N
d = pow(e, -1, (p - 1)*(q - 1))
m = pow(c, d, N)
from Crypto.Util.number import *
print(long_to_bytes(int(m)))
```

>**flag:** HTB{f4ct0r1ng_w1th_just_4_f3w_b1ts_0f_th3_CRT_3xp0n3nts!https://eprint.iacr.org/2022/271.pdf}

## Zombie Rolled

> With the formula now in your team's possession, you face a significant challenge. The formula is constructed upon an exceedingly advanced equation that surpasses your current comprehension of mathematics. A note suggests a flaw, but the intricacies appear much too complex. It's crucial for the equation to be genuinely secure and not break in edge cases. Can you analyze this complex equation to bring an end to this tragic situation once and for all?

`source.py`
```python
from Crypto.Util.number import getPrime, bytes_to_long
from fractions import Fraction
from math import prod
from hashlib import sha256
from secrets import randbelow

# I hope no one cares about Kerckhoff's principle :)
from secret import derive_public_key, FLAG


def fraction_mod(f, n):
    return f.numerator * pow(f.denominator, -1, n) % n


class PublicKey:

    def __init__(self, pub):
        self.pub = pub
        self.f = self.magic(pub)
        self.nb = (self.f.denominator.bit_length() + 7) // 8

    def encrypt(self, m):
        return pow(m, self.f.numerator, self.f.denominator)

    def verify(self, m, sig):
        s1, s2 = sig
        h = bytes_to_long(sha256(m.to_bytes(self.nb, "big")).digest())
        a, b = m, h
        r = self.encrypt(s1)
        c = self.encrypt(s2)
        return fraction_mod(self.magic((a, b, c)), self.f.denominator) == r

    @staticmethod
    def magic(ar):
        a, b, c = ar
        return Fraction(a, b) + Fraction(b, c) + Fraction(c, a)


class PrivateKey(PublicKey):

    def __init__(self, priv, pub):
        super().__init__(pub)
        if self.magic(priv) != self.f:
            raise ValueError("Invalid key pair")
        self.priv = priv
        self.d = pow(self.f.numerator, -1, prod([x - 1 for x in priv]))

    def decrypt(self, c):
        return pow(c, self.d, self.f.denominator)

    def sign(self, m):
        h = bytes_to_long(sha256(m.to_bytes(self.nb, "big")).digest())
        a, b = m, h
        c = randbelow(1 << self.nb)
        r = fraction_mod(self.magic((a, b, c)), self.f.denominator)
        s1 = self.decrypt(r)
        s2 = self.decrypt(c)
        return s1, s2

    @staticmethod
    def generate(nbits):
        while True:
            try:
                priv = tuple([getPrime(nbits) for _ in range(3)])
                pub = derive_public_key(priv)
                return PrivateKey(priv, pub)
            except ValueError:
                pass


def main():
    assert len(FLAG) <= 64
    m = bytes_to_long(FLAG)

    key = PrivateKey.generate(1024)
    data = f"pub = {key.pub}\n"

    # make sure it really works
    enc = key.encrypt(m)
    assert key.decrypt(enc) == m
    sig = key.sign(m)
    assert key.verify(m, sig)

    # mixing them :)
    mix = [sig[0] + sig[1], sig[0] - sig[1]]
    mix = [key.encrypt(x) for x in mix]
    data += f"mix = {mix}"

    with open("output.txt", "w") as f:
        f.write(data)


if __name__ == "__main__":
    main()
```

`output.txt`
```
pub = (-679149827688896546432684514781159016843208241259733038415608446794483893865137935606244643075224614588255514670703135466975466970913232919448971056463765967267932205245723609028669700790327888053737513173004906874791948794706164874163000233242663467112356684022940655151358257782991132383407917101800634477163967250047209128395271227452766580073122831277123102243705154267181506512402418039719173720827696442606604970528063168565779813124052546633103006687964589521475168208905582555230534709754318532234948569155566965251016769141273474050547777208930826949494111933369822606247918218189742785296490363879808413854798453415486721083351760540440727104905848109665720749305468099771944894375713469720303756620101486771358570935901473647573434771209918671576552610105681029288935780171889489032033021940200657122568137703155744190258342713712412791279147013204380608939099412456303268575877533613186703629403608506029153274825614231950222036769768440858686468414798013424323765200986740221652787739657510451377763752151261173701557996570807100005672113902309356835881522886670894124683065117547063607171455016275575226133973000898546626428525316423640294588059582425678225307973026140509236726222895277593502658123536454581741258241350319709620218946214665277722917625163484143287737560990362641167985157948128093478633332215687808673326510025966928820082371826010204772434810834908522239370003521927141674940923107924718589020116481655436804371698576313448620651274568714018039959665606305466508336068792512300330447079049391998121903628542300433666064229921580215835067999800174889123776199641564914760737600821953293714498453721075798275047009255942697608822458887368884228615671991262750899422262693397993810261279502497616420217291365257434988696383465213630804193810674969275499135895349396517300341694326130217329147937575188985044945549854453507141533817669145401648967346307076924815850188448842044898686784928539015228772201838211426957234107261809629639488140342429501192224120877838900600824055655563831019772958581350347081393942024086716044621385448218128870528968546874044728000006962970494893355352673283507468842799994294656025864551101856509528014985899203160260433154506345617010883888392862375738238905192516684056280710225432696655387350661830299769543107006018477779636761481812001885777307338070488998064732654317341303970152707374877928275562177058710127753099319277046999346991319018270073503529254012239006330090621492580837121246383935, -62076393535220713230211372773016154819776973272085279510651810925567779949353129946722665324346680014266486318715153201960740792449256110092242298637141869182191301801348471698842750492673269805386405258903922775271516275959471007270470783319809938344980989030858451767810558937769006054050097442784355926821230070379192555314422288851726919132047206078443630648828611342794053179031715333055698280449463491394083273880477510623203232349139229518535496279365263598613132079698328994631219260929529014155663831397189773051194410051588749692352737665469175569662944453505412436038259868251153299961933349760369897472364513246615362132669886696823052680175413762753451532554603896709930434997447861155715656069046149639262746319959746238179869888630675350046188185867904504017641487194302334327723755607524433514488406681046596968802669158589535873655500657057147028422069215178280598799698023523842357762349312690981167196137904038634376059163646848305423446194854985523208266633269733804799399607579721802215740994923964998077749276376011664190442983045021233328860583431325491398740404717916957583624416525249155775990424790364048060223994351310834002052812950138181225201366866314173709814344726454058400929651246654127831150461139153521780070432541564467256212600334416658092578286615407601260752708323965051595729545763555521745630970189873094019504197470499857463907805936270010647572788276449685684281797985033841971693243345694540270555091524839897049609610694927649006414175285808616559721550306026476105719357460921592355959150692653059587214522745084239909633381558412919917765258331958067109432311999676460754436818201240023311791169802096388297862219517745888298152485206376817292783209092101414045488990410966964411575840592166475524123368782667715013887175556663844953274929494297381264202051980047425186510319292378252632808136510156368067796082090881873863176619957188519245758562409606283750005034060349233190765189422774601110274372976258448740486162640303554541285690390642874100570657406690134508272681549727638076837266298852607372446728482637226932907561200761597573567176950238238261414346592107964949875193908993522612254840698757416962124520050724327233377985303460697496676181895430294259015494830416056429816116680627673230428123227550005209799526871524994673920145445281764352654447538983391735348123813851971146216997750793741632897137346289689048299843427272709114665386119324590006011517253390435055796456069897547662246663469050, 7830486145829508377558227408290134666403414818680891369176645894657730719560462345413647777035691365258303368067215176543275998927964954949936712360465415742305180023074796925974228987920346807337046972890048193675380148777690313974213732587383856306544481992804522271933234142582135631837143021763752036371144162337258022513117655962693146611295672219977744995976478481618309460588961678471875936477345228279163041888319063508894362501867902730894177063341454634424454782062634032617628985824505983759212857299828749995752235758280573881085074326788874659358083585790319249788052723728978677280818302159890013608215097544022503686764809254320162354127388898310054146477537454655331114752144580956313131601466170634129721047436217501010686515461442226299103956329529388095001070391385189826814864746360320333918929686149324974844514612649731799514398645742580625276435924292653289878873850265379360271050603859206438004883855432886650553014115919828422317294957226985270768975519537120458232477529774162488973095316791366021139626153997771391194348622350835846276300080551743631687665470499546808409919692234446116541139972549724015591941496221132694347053824568526998941915494190313922421141792712177850635304804853629529860738709134170021521102700328604628927875567294032063066171031633521264813050524393039049915547228949811888603458908961700972119089383547482638110428947454915597289453149684921808588155575892869551719408017163519505152564311034805403584244791464453674297092713220925531618514351406361895037695504970845118751396728421079695580995084470838685511076186640881628743480208128654755986540162228797182203097429878571055580982419910698271318560371375725624590383889293031827343721601921283218257563981664825233636121202430668321472633600201081855377049584327600961474706457007803948936824801776548939412019028877381656854352042881659133167174502453362966793813997989646037370005971535903722675701268453950616768210577721799262016472978512795971976360288455246627122308235526400041892494207712587655624310348101802715615388185255043745377008056152447575197765696754537890671347192620664952431667401070828663411172784617836559669320812607668877073449814900939924918858434664371233944816438544081459935797541138873496291375791704149531348074915907756350500006241339201669154574473335111528050064627331874217370202929428747238792910498913366471880111057711710182734506773946328772476941505432386559666283038340899500750365525879310436221464632932)
mix = [320097670159304209872309549471930993417143388077612479497520462342663934977666619462544724222917378443968580805422580825361629081490271425796180005007150747981294833573665302687843134857904098195466433204039058015530152552325684827139190765614362244643766711802006240778166882116884363374725459551909065104305033828828854239454604753713577854447476580313858442898211797176449603725270483947895282965175447381948570185109212852155846189091780576621552251326144055737198080348963003230356855332599698331598994549981281612662608348841085666739363070407550100107611043508427316142601784200258788876751333421411315222696828075445887895455540798110282831853710380354729703219505108629359197051336831375142595785378662848001960222878291497005997865756885831339332471275732796885611507451753622271015336628712225776167393560183733898486530850953580390452793438185371865569655507768389802668508433717577899938841236692983646823027361, 349362680724100439001784418744128033820777004106762219940030797663423035126842789961301765825984790980690057949860749399355278228450692377224412215897507215190015109832483673402204604232784701242900565985678019980900605960003183528535025133581067044874804468687010880739758605406077761031350401248992459970793003097988643424534679899913404629630391199540164124586883472387639995010304310446530364782969667797609685774685940403053235307671556459857310584515167273597080744839887558160920716970032955099943334650018552259294137522684003806871855045036984303040298988654317845297423677227040203951111165862748444474516781528350286768742283071330157900311167729282004019891879048809487509974878771126244472706852271120669147203065908414503160273025384004067262219422586521719641783916408458322110103608436662590492232330298616191719127365835397875038644290248233827642595125378514596518087399154247594983235918109876187387712452]
```

A challenge with weird encryption and signing method! In this challenge, the source code is mentioned to [Kerckhoff's principle](https://en.wikipedia.org/wiki/Kerckhoffs%27s_principle), and we have everything except `private_key`. We are also given `mix`, which is the list contain encrypted `sig[0] + sig[1]` and `sig[0] - sig[1]`, where `sig[0], sig[1]` is the result of signing `flag`

### From an equation to an elliptic curve :astonished: 

In this challenge, the $public\_key = (a_1, b_1, c_1)$ and $private\_key = (a_2, b_2, c_2)$ have a relationship

$$f = magic(a_1, b_1, c_1) = magic(a_2, b_2, c_2) = {fn \over fd}$$

where $$magic(a, b, c) = {a \over b} + {b \over c} + {c \over a}$$
With a strange equation, I try to google to have some ways to deal with it, after about one hours, I find this one: https://mathoverflow.net/questions/27107/transforming-a-diophantine-equation-to-an-elliptic-curve

It looks like a magic !

We will using the $public\_key$ and the result in the site. Let:

$$\begin{aligned}u &= 3*{f^2z - 12x \over z} \\ v &= 108*{2xy-fxz+z^2 \over z^2}\end{aligned}$$

Then $(u, v)$ is a rational point on the elliptic curve $(E_f): v^2 = u^3 + Au + B$ with $A = 27f(24 - f^3)$ and $B = 54(216 - 36f^3 + f^6)$

With this way, we can generate many tuples $(a_i, b_i, c_i)$ that satisfy $magic(a_i, b_i, c_i) = f$ and vice versa, with a tuple $(x, y, z)$ that satisfy the equation $magic(x, y, z) = f$, we can construct a point $G(u, v)$ that lying on $(E_f)$

### Finding private key

With the relationship we have found, we will treat $private\_key$ as a point $G_{priv}$ in $(E_f)$. Because this challenge has a hidden function `derive_public_key()`, so I guess $G_{priv}$ generates the $public\_key$ as a point $G_{pub}$ by some method

If we read the code carefully, we will see that $0 < a_2, b_2, c_2 < 2^{1024}$, when $a_1, b_1, c_1 \approx 2^{8192}$, so we need to find a way to decrease the point $G_{pub}$. And surprisingly, SageMath has a magical method $.division\_point()$. When we calculate $G_{pub}.division\_point(2)$, we will have the point $G_{priv}!$

> This part is a little bit confused, maybe because I solved this part with a guessy way. 

### Finding the signature

When we have $private\_key$, we can find the signature

Reading the source code, we have

$$\begin{aligned}mix_0 &= key.encrypt(sig_0 + sig_1) \\ mix_1 &= key.encrypt(sig_0 - sig_1)\end{aligned}$$

where $$\begin{aligned}sig_0 &= \Big({flag \over sha256(flag)} + {sha256(flag) \over c} + {c \over flag}\Big)^d \\ sig_1 &= c^d\end{aligned}$$

and $d$ can calculate from $private\_key$

(Remember $sig_0$ and $sig_1$ are in $GF(fd)$)
Because we have $private\_key$, we can decrypt $mix_0$ and $mix_1$ and get $sig_0$ and $sig_1$

### Finding the flag

Because we can calculate $e$ from $public\_key$, so by encrypt $sig_0$ and $sig_1$, we will have the equation

$$k = sig_0^e = {flag \over sha256(flag)} + {sha256(flag) \over c} + {c \over flag} \mod fd$$

with known $k$ and $c = sig_1^e \mod fd$, we can change it to the equation

$$k*c*flag*sha256(flag)= flag^2*c + sha256(flag)^2*flag + c^2*sha256(flag) \mod fd$$

Because `len(FLAG) < 64`, so $flag < 2^{64*8} = 2^{512}$. We also have $sha256(flag) < 2^{256}$, when $fd = a_1*b_1*c_1 \approx 2^{3072}$, so we can use bivariate coppersmith with function

$$g(x, y) = x^2c + y^2x + c^2y - kcxy \mod fd$$

to find roots $(x_0, y_0) = (flag, sha256(flag))$

### The code, after all

`solve.sage`
```python
from sage.all import *
from Crypto.Util.number import *
from fractions import Fraction
from math import prod
from hashlib import sha256
import itertools

#Step 1
pub = ()
x, y, z = pub

#f = magic(x, y, z)
f = QQ(3701525472893754239556659146967419294548810105088021761582278341178984148706714849495354572560649412946184663258088912346600083850901512993954559285104856171191192277217584795914461587554538912171921135802509605569817764285175424876824473380387225760087531693029912774720506196487387886048720092146542181664493995438130886554006434662910173182386189855173126151273965559205502505991343826706689240290992604129443515094557601688161362570936067972161997111669004650102495491760302561536310799959359506310468970488806518882801567577729407854267148259783351024268951798970899778594609519014411999199382407587199450660822086130822218329160084330279562341477826514749124374700145975627180419620707989403280355093770570604229574635834749773398475083608003053221351055125031829169474647676191591060731048302412840041136589207808630461233905329929617648945992105567023376117108474814613664279799015617517748741353656353240603055262833/1233228582347016947966224073972268144554236820892942499519122000771084166375804575410256498849474001839664438201612077539125732335938974180546116101865678145343967028533823514484007970750393340966594511217807050165719060909011954955474682851519212527228087532500877286327027558523551790732475822426937017444940866615592974442092482434411876518875851004346287404070582765235948489547282744947043755585727828924938737667298963460848381817651284299657969882102172228225806548854044352691115222585573695801350283013181198024885246537475115476935052466323229708374943236919527896588729386077465925760832854469559234925755079146251766439063105010673262343403050052003476148441220953810088244785797541039087544841861476346530581157709458126406121980561603659803563500884868073661022084810883103625906984936967388404739473783378705660995543572971451987404675428545121985331638196771442992280022247133820096024932788948323579723802871)
A = 27 * f * (24 - f**3)
B = 54 * (216 - 36 * f**3 + f**6)
mix = [..]

E = EllipticCurve(QQ, [A, B])

u = QQ((3 * f * f * z - 36 * x)/z)
v = QQ(108*(2 * x * y - f * x * z + z^2)/z^2)
P = E(u, v)

def find_abc(G):
    u, v = G.xy()
    x_z = QQ((3*n*n - u) / 36)
    x = x_z.numerator()
    z1 = x_z.denominator()

    y_z = QQ((v / 108 + n * x_z - 1) / (2 * x_z))
    y = y_z.numerator()
    z2 = y_z.denominator()
    if z1 != z2:
        z = lcm(z1, z2)
        x *= (z//z1)
        y *= (z//z2)
    else:
        z = z1 = z2
    return x, y, z

#Step 2

P = E(P.division_points(2)[0]) #Magic!
x, y, z = find_abc(P)
d = pow(f.numerator(), -1, (x - 1) * (y - 1) * (z - 1))

#Step 3

mix = [pow(c, d, f.denominator()) for c in mix]
sig0_dec = (mix[0] + mix[1])//2
sig1_dec = (mix[0] - mix[1])//2

sig0 = pow(sig0_dec, f.numerator(), f.denominator())
sig1 = pow(sig1_dec, f.numerator(), f.denominator())

#https://github.com/defund/coppersmith/blob/master/coppersmith.sage
#With a little change
def defund_multivariate(f, bounds, m=1, d=None):
    if not d:
        d = f.degree()
    R = f.base_ring()
    N = R.cardinality()
    #f /= f.coefficients().pop(0)
    f = f.change_ring(ZZ)
    G = Sequence([], f.parent())
    for i in range(m+1):
        base = N^(m-i) * f^i
        for shifts in itertools.product(range(d), repeat=f.nvariables()):
            g = base * prod(map(power, f.variables(), shifts))
            G.append(g)
    B, monomials = G.coefficient_matrix()
    monomials = vector(monomials)
    factors = [monomial(*bounds) for monomial in monomials]
    for i, factor in enumerate(factors):
        B.rescale_col(i, factor)
    B = B.dense_matrix().LLL()
    B = B.change_ring(QQ)
    for i, factor in enumerate(factors):
        B.rescale_col(i, 1/factor)
    H = Sequence([], f.parent().change_ring(QQ))
    for h in filter(None, B*monomials):
        H.append(h)
        I = H.ideal()
        if I.dimension() == -1:
            H.pop()
        elif I.dimension() == 0:
            roots = []
            for root in I.variety(ring=ZZ):
                root = tuple(R(root[var]) for var in f.variables())
                roots.append(root)
            return roots
    return []

#Step 4
c = sig1
F.<m, h> = PolynomialRing(Zmod(f.denominator()), 2)
g = m^2*c + h^2*m + c^2*h - c * sig0 * h * m
flag_hash = defund_multivariate(g, (2^(64*8), 2^256), m = 3, d = 4)[1]
print(long_to_bytes(int(flag_hash[0])).decode())
```

>**flag:** HTB{4_s3cur3_crypt0syst3m_sh0u1d_n0t_c0nt41n_s3cr3t_c0mp0n3nts!}



| Column 1 | Column 2 | Column 3 |
| -------- | -------- | -------- |
| Text     | Text     | Text     |
