---
# System prepended metadata

title: pbctf2023 writeup

---

# pbctf2023 writeup
- Crypto (blocky4, remake)

## blocky4

part of source (`task.py`)

```python=
def main():
    signal.signal(signal.SIGALRM, handler)
    signal.alarm(60)
    key = get_key()

    with open('flag', 'rb') as f:
        flag = f.read()
        flag += b'\x00' * ((-len(flag)) % 9)
    
    flag_cipher = BlockCipher(key, 20)
    enc_flag = b''
    for i in range(0, len(flag), 9):
        enc_flag += flag_cipher.encrypt(flag[i:i+9])
    print(f"enc_flag: {enc_flag.hex()}")

    cipher = BlockCipher(key, 4)
    while True:
        inp = bytes.fromhex(input("> "))
        assert len(inp) < (3 ** 7)
        assert all(b < 243 for b in inp)

        enc = b''
        for i in range(0, len(inp), 9):
            enc += cipher.encrypt(inp[i:i+9])
        
        print(f"Result: {enc.hex()}")

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

part of source (`Cipher.py`)

```python
from GF import GF

SBOX, INV_SBOX = dict(), dict()
for i in range(3 ** 5):
    v = GF(23) + (GF(0) if i == 0 else GF(i).inverse())
    SBOX[GF(i)] = v
    INV_SBOX[v] = GF(i)

class BlockCipher:
    def __init__(self, key: bytes, rnd: int):
        assert len(key) == 9
        sks = [GF(b) for b in key]
        for i in range(rnd * 9):
            sks.append(sks[-1] + SBOX[sks[-9]])
        self.subkeys = [sks[i:i+9] for i in range(0, (rnd + 1) * 9, 9)]
        self.rnd = rnd

    def _add_key(self, l1, l2):
        return [x + y for x, y in zip(l1, l2)]

    def _sub_key(self, l1, l2):
        return [x - y for x, y in zip(l1, l2)]
    
    def _sub(self, l):
        return [SBOX[x] for x in l]

    def _sub_inv(self, l):
        return [INV_SBOX[x] for x in l]
    
    def _shift(self, b):
        return [
            b[0], b[1], b[2],
            b[4], b[5], b[3],
            b[8], b[6], b[7]
        ]
    
    def _shift_inv(self, b):
        return [
            b[0], b[1], b[2],
            b[5], b[3], b[4],
            b[7], b[8], b[6]
        ]
    
    def _mix(self, b):
        b = b[:] # Copy
        for i in range(3):
            x = GF(7) * b[i] + GF(2) * b[3 + i] + b[6 + i]
            y = GF(2) * b[i] + b[3 + i] + GF(7) * b[6 + i]
            z = b[i] + GF(7) * b[3 + i] + GF(2) * b[6 + i]
            b[i], b[3 + i], b[6 + i] = x, y, z
        return b
    
    def _mix_inv(self, b):
        b = b[:] # Copy
        for i in range(3):
            x = GF(86) * b[i] + GF(222) * b[3 + i] + GF(148) * b[6 + i]
            y = GF(222) * b[i] + GF(148) * b[3 + i] + GF(86) * b[6 + i]
            z = GF(148) * b[i] + GF(86) * b[3 + i] + GF(222) * b[6 + i]
            b[i], b[3 + i], b[6 + i] = x, y, z
        return b
    
    def encrypt(self, inp: bytes):
        assert len(inp) == 9
        b = [GF(x) for x in inp]
        
        b = self._add_key(b, self.subkeys[0])
        for i in range(self.rnd):
            b = self._sub(b)
            b = self._shift(b)
            if i < self.rnd - 1:
                b = self._mix(b)
            b = self._add_key(b, self.subkeys[i + 1])
        
        return bytes([x.to_int() for x in b])
    
    def decrypt(self, inp: bytes):
        assert len(inp) == 9
        b = [GF(x) for x in inp]

        for i in reversed(range(self.rnd)):
            b = self._sub_key(b, self.subkeys[i + 1])
            if i < self.rnd - 1:
                b = self._mix_inv(b)
            b = self._shift_inv(b)
            b = self._sub_inv(b)
        b = self._sub_key(b, self.subkeys[0])

        return bytes([x.to_int() for x in b])
```

(`GF.py` is skipped: an implementation of operations over $GF(3^5)$)

This challenge task is to decrypt encrypted flag with original block cipher on by using arbitrary known plaintext-ciphertext pairs (encryption oracle). This cipher is based on AES, but over $GF(3^5)$ instead over $GF(2^8)$. 

The known effective attack for AES 4-round is [Square attack (from davidwong.fr)](https://www.davidwong.fr/blockbreakers/square_2_attack4rounds.html). It uses the property that sum of some set of 3-round (pre last round) encrypted plaintexts equals 0. This can be applied if:

- sum of all elements of base field equals 0 (all finite field satisfied)
- mixcolumn is one row linear multiplication
- sbox is permutation of all elements of base field

Then, we can apply the Square attack directly. Though I experienced I needed a few multiple sets for reducing key space on similar other challenge, this challenge needs only one set ($3^5$ plaintext-ciphertext pairs).

Here is the code.

```python=
from typing import List
import itertools

from pwn import *

from GF import GF
from Cipher import SBOX, INV_SBOX, BlockCipher

proc = remote('blocky-4.chal.perfect.blue', 1337)

encflag = bytes.fromhex(proc.recvline().strip().split(b': ')[1].decode())
(proc.recvuntil(b'> '))

ctlst = []
for i in range(3**5):
    pt = bytes([i]) + bytes([0 for _ in range(1, 9)])
    proc.sendline(pt.hex().encode())
    ctlst.append(bytes.fromhex(proc.recvline().strip().split(b': ')[1].decode()))

print('gen ctlst done')

def _sub_key(l1, l2):
    return [x - y for x, y in zip(l1, l2)]

def _shift_inv(b: List[GF]) -> List[GF]:
    return [
        b[0], b[1], b[2],
        b[5], b[3], b[4],
        b[7], b[8], b[6]
    ]

def _sub_inv(l: List[GF]) -> List[GF]:
    return [INV_SBOX[x] for x in l]

def after_shift_idx(i):
    return [0, 1, 2, 5, 3, 4, 7, 8, 6][i]

lastkeycand = [[] for _ in range(9)]
for i in range(3**5):
    keycand = [GF(i) for _ in range(9)]
    after_3round_result = [GF(0) for _ in range(9)]
    for ct in ctlst:
        state = [GF(ele) for ele in ct]
        state = _sub_key(state, keycand)
        state = _shift_inv(state)
        state = _sub_inv(state)
        for j in range(9):
            after_3round_result[j] += state[j]
    for j in range(9):
        if after_3round_result[j] == GF(0):
            lastkeycand[after_shift_idx(j)].append(GF(i))
print(lastkeycand)

rnd = 4
for key in itertools.product(*lastkeycand):
    subkeys = [None for i in range(0, rnd * 9)] + list(key)
    for i in range((rnd+1)*9-1, 9-1, -1):
        subkeys[i-9] = INV_SBOX[subkeys[i] - subkeys[i-1]]
    sks = bytes([x.to_int() for x in subkeys[:9]])
    cipher = BlockCipher(sks, 4)
    flag_cipher = BlockCipher(sks, 20)
    pt = b''
    for chunk in range(0, len(encflag), 9):
        pt += (flag_cipher.decrypt(encflag[chunk:chunk+9]))
    print(pt)
```

## remake (unsolved during the CTF)

source (`task.py`)
```python=
import os 

flag = open("flag", "rb").read()
flag = flag.lstrip(b"pbctf{").rstrip(b"}")
assert len(flag) == 192

while True:
    p = random_prime(1 << 513, lbound = 1 << 512)
    coefs = [int.from_bytes(os.urandom(42), "big") for _ in range(8)]
    PR.<x> = PolynomialRing(GF(p))

    g1, g2 = 2, 3
    f1 = sum(coefs[i] * (x ** i) for i in range(2 * g1 + 2))
    f2 = sum(coefs[i] * (x ** i) for i in range(2 * g2 + 2))

    flag1 = GF(p)(int.from_bytes(flag[:64], "big"))
    flag2 = GF(p)(int.from_bytes(flag[64:128], "big"))
    flag3 = GF(p)(int.from_bytes(flag[128:], "big"))
    hint = GF(p)(int.from_bytes(b"Inspired by theoremoon's SECCON 2022 Finals Challenge - Hell. Thank you!", "big"))

    pol1 = x * x - f1(flag1)
    pol2 = x * x - f1(flag2)
    pol3 = x * x - f2(flag3)
    pol4 = x * x - f2(hint)

    if len(pol1.roots()) * len(pol2.roots()) * len(pol3.roots()) * len(pol4.roots()) == 0:
        continue 

    HC1 = HyperellipticCurve(f1, 0)
    J1 = HC1.jacobian()(GF(p))

    HC2 = HyperellipticCurve(f2, 0)
    J2 = HC2.jacobian()(GF(p))

    P1 = HC1((flag1, pol1.roots()[0][0]))
    P2 = HC1((flag2, pol2.roots()[0][0]))
    P3 = HC2((flag3, pol3.roots()[0][0]))
    P4 = HC2((hint, pol4.roots()[0][0]))

    print(2 * J1(P1) + 2 * J1(P2))
    print(5 * J2(P3))
    print(J2(P4))
    break
```

This challenge is related to hyperelliptic curves. We need to recover defined polynomial and find flag which is encoded to a small multiple of jacobian of point representation.

I was not so familiar with hyperelliptic curve, so I looked at [Imaginary hyperelliptic curve (from wikipedia)](https://en.wikipedia.org/wiki/Imaginary_hyperelliptic_curve). Then, I remembered the jacobian group is the quotient of divisor group. And I knew the divisor group on elliptic curve is normal elliptic curve group with addition formula.

Then, I tried to understand how the jacobian group is generalized from addition formula on elliptic curve. The wikipedia says reduced divisors (that is, elements of the jacobian group) are represented as Mumford representation.

A divisor $D$ is uniquely represented as $D=(u(x), y-v(x))$ with $u(x), v(x) \in K[x]$ such as

- $\deg{v} < \deg{u} \le g$
- $u(x)$ is monic
- $((v(x)^2 + h(x)*v(x)) - f(x)) \mod{u(x)} = 0$
where the hyperelliptic curve is defined as $y^2+h(x)*y=f(x)$ over $K$ for $\deg{f}=2*g+1, \deg{h}\le g$.

(Note that some article uses $D=(u(x), v(x))$ cause it is determined only $u,v$. On Sagemath, a divisor is printed as our representation, but $Jac(P)[1]$ returns $v(x)$.)
From now, assume $h(x)=0$.

On elliptic curve, each non-infinity point $P=(x_0, y_0)$ corresponding to the divisor $(x-x_0, y-y_0)$. (It is written as $[P]-[\infty]$.) This seems same on hyperelliptic curve. In fact, all non-infinity points on hyperelliptic curve has similar representation.

Then, I experimented for $[i]*Jac(P)$ on $i\le g+1$. I realized

- For $i\le g$, $[i]*Jac(P)[0] = (x-x_0)^i$
- For $i=g+1$, not equal to $(x-x_0)^i$ anymore

For elliptic curve, doubling formula is based on cross point of the tangent line of $P$. So I assume that it is related to some Taylor expansion of $y=\sqrt{f(x)}$. So I put as
$\bar{v}(x)=-(y_0 + \frac{1}{1!}*\frac{dy}{dx}(P)*(x-x_0) + \frac{1}{2!}*\frac{d^2y}{dx^2}(P)*(x-x_0)^2 + \ldots + \frac{1}{g!}*\frac{d^gy}{dx^g}(P)*(x-x_0)^g)$ and confirmed

$(x-x_0)^{(g+1)}*u(x) = (f(x) - {\bar{v}(x)}^2)/\mathbb{lc}(f)$. ($\mathbb{lc}(f)$ is leading coefficient of $f$)
(I divides $\mathbb{lc}(f)$ for $u(x)$ as monic.)
I also confirm that similar formula is satisfied on $i=g+2$. (but we need to divide $-\mathbb{lc}(\bar{v})^2$ instead of $\mathbb{lc}(f)$.)

During the CTF, I applied this formula **without knowing $f(x)$**, but we did not find $f(x)$.

After the CTF, we saw the [writeup](https://rkm0959.tistory.com/285) by @rkm0959. We really depressed that finding $f(x)$ is easy by using LLL and Mumford representation rule. I forgot the check for bit length of coefficients of $f(x)$ are fairly small compared to $p$. Maybe the reason is that I too focused on hyperelliptic curve thing...

However, I notice that @rkm0959 approach uses equation similar to us. But direct substituting $x_0, y_0$ is really wasted time. I rethinks how to solve equation.

Firstly considering $(U2(x), y-V2(x))=5*J2(P3)$ ($P3=(a, \sqrt{f(a)})$, $a=\mathbb{flag3}$). Since this is $5=g_3+2$ case, I have the following formula.

$(x-a)^5 * U2(x) = (f(x)-\bar{V2}(x))/(-\mathbb{lc}(\bar{V2})^2)$.

$\deg{\bar{V2}}=5-1, \deg{U2}=3(=g_3), \deg{f}=7(=2*g_3+1)$. By Mumford represention rule, $f(x)={\bar{V2}(x)}^2 = {V2(x)}^2 \mod{U2(x)}$. Then, I write as

$\bar{V2}(x) = -V2(x) + (A+B*x)*U2(x)$
with unknown $A,B$ and $\mathbb{lc}(\bar{V2})=B$. (I uses $-V2(x)$ cause it is just similar to elliptic curve addition formula. Which one uses does not affect the system.)
By substituting $\bar{V2}(x)$ to above equation and comparing each terms for $1,x,x^2,\ldots,x^8$, we have 9 equations with unknown variable $A, B, a$. (The degree 8 equation is totally useless.) It can be solved by Sagemath.

Then, switch to $(U1(x), y-V1(x))=2*J1(P1)+2*J1(P2)$, ($P1_x=b=\mathbb{flag1}, P2_x=c=\mathbb{flag2}$).
Just thinking similar to above formula,

${((x-b)*(x-c))}^2 * U1(x) = (f(x)-{\bar{V1}(x)}^2)/(-\mathbb{lc}(\bar{V1})^2)$

By using symmetricity, write $\alpha=b+c,  \beta=b*c$.

$(x^2-\alpha*x+\beta)^2*U1(x)=(f(x)-{\bar{V1}(x)}^2)/(-\mathbb{lc}(\bar{V1})^2)$

By substituting $\bar{V1}(x) = -V1(x) + (A+B*x)*U1(x)$ as just above, we have 7 equations with unknown variable $A, B, \alpha, \beta$. Unfortunatelly, Sagemath cannot solve the system (maybe Singular issue over the finitefield with large prime characterstic), so I tossed the system to [Magma online calculator (from magma.maths.usyd.edu.au)](http://magma.maths.usyd.edu.au/calc/). It solved the system instantly. Then, I find the solution $b,c$ with quadratic univariate equation from $\alpha,\beta$. so I obtained all flag.

```python=
## precompute by J2_P4[0].subs({x:hint, y:y}) = 0 mod p
p = 20206629497318640302613717177493021659164910368667008120702312168658959729889280963268869447056316491892128508524975162223724948508181049113205403468161303

PR = PolynomialRing(GF(p), 2, ["x", "y"])
x,y = PR.gens()

## from output.txt
two_J1_P1_two_J1_P2 = (x^2 + 14762123602851553604749022996287576858980220577795651427829269858766434621297346961387874961427459051934768224338447011128244905975068497090840444625419470*x + 8519674750729750620690035589812482119785861876353468044895414394332293279114303071755954851101633319350193436546144692795403444364414318973131157246232656, y + 17770738679279057916355557895675090129563269633432826251932824463003364931275912702916209480950481351904761364290424406482997835483807402182326014818733821*x + 12306668467523337827805393760490897581559948654643366727345701375757143864825442910779617850907143245102792529282031529618639723158417652048624567379151171)
five_J2_P3 = (x^3 + 13441279154284544764330805782065565325543470739559917045273482055514440837785754044182874902421009026981197721504820302867945812937528249594953326223176272*x^2 + 3795282115520834934850220740151212731596814319504043674340537364041453624883995759365119899076774262882230308591629439035308527946872182029742910504122735*x + 3726617245981099594981815385059428688276726297460965450328320328460867196111587736356492934195556032891106446058683147130913147722036293641303193921962091, y + 2103349591221335944593862709600493681857281410337020721978302326614691696399677635217262732543672829811190387220058078405239568477387817550236173432744263*x^2 + 4784247634355946154999459446762911004042472267922959302672838559247991353014786987556174410735592161587023899368989617780068662559773261109676326152316907*x + 2640959823121300693709616791657128464111647959613642856293592234010564318329382577397798309822254798484629398268742247779165733848105319417195858443049412)
J2_P4 = (x + 540914350057159927735436910109553086959907629357396102386062800609858847095404691934517983640846787552171946520460064397364544720996858815571065665255645, y + 541917331856005964100090629475512429550322452567752818120774876171019476274441296070275457561095853517207532108745504694853066426720092700847788666013730)

U1 = two_J1_P1_two_J1_P2[0]
V1 = -two_J1_P1_two_J1_P2[1].subs({x:x, y:0})
U2 = five_J2_P3[0]
V2 = -five_J2_P3[1].subs({x:x, y:0})
U3 = J2_P4[0]
V3 = -J2_P4[1].subs({x:x, y:0})

hint = GF(p)(int.from_bytes(b"Inspired by theoremoon's SECCON 2022 Finals Challenge - Hell. Thank you!", "big"))

assert J2_P4[0].subs({x:hint, y:y}) == GF(p)(0)

g1, g2 = 2, 3


### step1: compute defining polynomial for hyperelliptic curve
hc_def_pol_coef_rng = PolynomialRing(GF(p), 2*g2+2, ["c%d" % i for i in range(2*g2+2)])
ci = hc_def_pol_coef_rng.gens()

hc_def_pol_rng = PolynomialRing(hc_def_pol_coef_rng, "X")
X = hc_def_pol_rng.gens()[0]

f1symbpol = sum(ci[i] * (X ** i) for i in range(2 * g1 + 2))
f2symbpol = sum(ci[i] * (X ** i) for i in range(2 * g2 + 2))

## Mumford representation relation: f = V^2 mod U
mumford_rel_1 = (f1symbpol - V1.subs({x:X, y:0})^2).quo_rem(U1.subs({x:X, y:0}))[1].list() # g1-equations
mumford_rel_2 = (f2symbpol - V2.subs({x:X, y:0})^2).quo_rem(U2.subs({x:X, y:0}))[1].list() # g2-equations
mumford_rel_3 = (f2symbpol - V3.subs({x:X, y:0})^2).quo_rem(U3.subs({x:X, y:0}))[1].list() # 1-equations

mumford_rel = mumford_rel_1 + mumford_rel_2 + mumford_rel_3
## linear equations, but 2-dim shortage... but each coefficients are (42*8) bits << 512bits -> LLL!

## from rkm Inequality_Solving_with_CVP
load("/home/sage/inequ_cvp_solve.sage")

coef, monos = Sequence(mumford_rel).coefficient_matrix()

mat = []
linequlen = coef.nrows()
linequvars = 2 * g2 + 2
assert coef.ncols() == linequvars + 1

for i in range(linequlen):
    mat.append([int(coef[i][j].lift()) for j in range(linequvars+1)] + [0]*i + [p] + [0]*(linequlen-i-1))
for i in range(linequvars):
    mat.append([0]*i + [1] + [0]*(linequvars-i-1) + [0] + [0]*linequlen)
mat.append([0]*linequvars + [1] + [0]*linequlen)

mat = matrix(ZZ, mat).transpose()
lb = [0]*linequlen + [0]*linequvars + [1]
ub = [0]*linequlen + [2**(8*42)]*linequvars + [1]

print("computing def poly by LLL")
sols = solve(mat, lb, ub)
sol = sols[2] # var solution (sols[0] is equ solution)

f1 = sum(GF(p)(sol[i]) * (X ** i) for i in range(2 * g1 + 2))
f2 = sum(GF(p)(sol[i]) * (X ** i) for i in range(2 * g2 + 2))

assert (f1 - V1.subs({x:X})^2) % U1.subs({x:X}) == 0
assert (f2 - V2.subs({x:X})^2) % U2.subs({x:X}) == 0
assert (f2 - V3.subs({x:X})^2) % U3.subs({x:X}) == 0


# step2: find flag3
polsymbrng_flag3 = PolynomialRing(GF(p), 3, ["A", "B", "a"])
A, B, a = polsymbrng_flag3.gens()
hc_def_pol_flag3 = PolynomialRing(polsymbrng_flag3, "X")
X_flag3 = hc_def_pol_flag3.gens()[0]

## v_bar_flag3: deg=4 (for reducing (X-a)^5), f=v_bar_flag3^2 = V2^2 mod U2
v_bar_flag3 = -V2.subs({x:X_flag3}) + (A + B*X_flag3) * U2.subs({x:X_flag3})
f2symbpol_flag3 = sum([GF(p)(ele) * X_flag3^i for i, ele in enumerate(f2.list())])
lefthand = f2symbpol_flag3 - v_bar_flag3^2
righthand = -B^2 * (X_flag3-a)^5 * U2.subs({x:X_flag3})
lefthand_list = lefthand.list()
righthand_list = righthand.list()

assert len(lefthand_list) == (1+3)*2 + 1 # deg8
assert len(righthand_list) == 5 + 3 + 1 # deg8

equ = []
for i in range(len(lefthand_list)):
    equele = lefthand_list[i] - righthand_list[i]
    equ.append(equele)

I_flag3 = polsymbrng_flag3.ideal(equ)
print("computing flag3")
V_flag3 = I_flag3.variety()
print(V_flag3)
flag3_root = V_flag3[0][a]

flag3 = int.to_bytes(int(flag3_root.lift()), 64, 'big')
print(flag3)


# step3: find flag1, flag2
polsymbrng_flag12 = PolynomialRing(GF(p), 4, ["A", "B", "a", "b"])
A, B, a, b = polsymbrng_flag12.gens()
hc_def_pol_flag12 = PolynomialRing(polsymbrng_flag12, "X")
X_flag12 = hc_def_pol_flag12.gens()[0]

## v_bar_flag12: deg=3 (for reducing (X^2-a*X+b)^2), f=v_bar_flag12^2 = V1^2 mod U1
v_bar_flag12 = -V1.subs({x:X_flag12}) + (A + B*X_flag12) * U1.subs({x:X_flag12})
f1symbpol_flag12 = sum([GF(p)(ele) * X_flag12^i for i, ele in enumerate(f1.list())])
lefthand = f1symbpol_flag12 - v_bar_flag12^2
righthand = -B^2 * (X_flag12^2 - a*X_flag12 + b)^2 * U1.subs({x:X_flag12})
lefthand_list = lefthand.list()
righthand_list = righthand.list()

assert len(lefthand_list) == (1+2)*2 + 1 # deg6
assert len(righthand_list) == 2*2 + 2 + 1 # deg6

equ = []
for i in range(len(lefthand_list)):
    equele = lefthand_list[i] - righthand_list[i]
    equ.append(equele)

I_flag12 = polsymbrng_flag12.ideal(equ)
print("computing flag12")
G_flag12 = I_flag12.groebner_basis()


## for Magma program
G12out = open('G12out.txt', 'w')
G12out.write(f"p:={p};\n")
G12out.write(f"P<A, B, a, b>:=PolynomialRing(GF(p), 4);\n")
G12out.write(f"I := ideal<P |\n")
G12out.write(f"{str(G_flag12[:5])[1:-1]}>;\n")
G12out.write(f"Variety(I);\n")

## Sage9.7 variety does not compute solution... instead use Magma Online calc
#V_flag12 = I_flag12.variety()
#print(V_flag12)
sol = [(13251201369422734297366526607962697431123459669915815849542141065457700206210679127885890919842739624682727419516349360728543108726009316584250248278486350, 16729053782777399746765327111114721998547116314370943300162668871471785975926363509665997824645808370479979293664335945059595374048602182572228181517703089, 5224076322553305114437830665526577139615879548772477449710363729574538835829096345899844681709029008011430318958967087007237927563994860535590585262112322, 1508986926034823363823763034485115530088825680173293957835708459247418883522363409626628316500047427774631513161686709517586272538191205520278401195650306)]


flag12_add_root = GF(p)(sol[0][2])
flag12_mul_root = GF(p)(sol[0][3])

flag12_pol = PolynomialRing(GF(p), "flag12")
flag12 = flag12_pol.gens()[0]
flag12_roots = (flag12^2 - flag12_add_root * flag12 + flag12_mul_root).roots()
print(flag12_roots)

flag1 = int.to_bytes(int(flag12_roots[0][0].lift()), 64, 'big')
print(flag1)
flag2 = int.to_bytes(int(flag12_roots[1][0].lift()), 64, 'big')
print(flag2)
```

Above method can be applied repeatedly for more general challenge. The method would be justified by [Group Law Computations on Jacobians of Hyperelliptic Curves](https://eprint.iacr.org/2011/306.pdf). The jacobian addition is related to tangent curve and crossing curve (interpolating polynomial).
@rkm0959 points out this challenge is releated to divide-2 for genus 3 problem. It requires 2 steps above method. (and divide-3 for genus 3, 3 steps.) I am thinking rkm method can be worked with reducing variables. (instead of vanishing out $T(x)$, but positively writes $T(x)$ with $u(x), v(x)$.)

Anyway, thank you for nice challenges to the author @rbtree, @rkm0959.
