---
# System prepended metadata

title: 'Bi0sCTF 2024 - Writeup '
tags: [CTF]

---

### lalala

``Source.py``
```python
from random import randint
from re import search

flag = "bi0sctf{ %s }" % f"{randint(2**39, 2**40):x}"

p = random_prime(2**1024)
unknowns = [randint(0, 2**32) for _ in range(10)]
unknowns = [f + i - (i%1000)  for i, f in zip(unknowns, search("{(.*)}", flag).group(1).encode())]

output = []
for _ in range(100):
    aa = [randint(0, 2**1024) for _ in range(1000)]
    bb = [randint(0, 9) for _ in range(1000)]
    cc = [randint(0, 9) for _ in range(1000)]
    output.append(aa)
    output.append(bb)
    output.append(cc)
    output.append(sum([a + unknowns[b]^2 * unknowns[c]^3 for a, b, c in zip(aa, bb, cc)]) % p)

print(f"{p = }")
print(f"{output = }")
```

Bài này flag được ẩn trong **unknowns** bao gồm 10 giá trị với 100 vòng for :

Ta hãy để ý

```


output.append(sum([a + unknowns[b]^2 * unknowns[c]^3 for a, b, c in zip(aa, bb, cc)]) % p)


```

Ở vòng for đầu tiên ta sẽ có coefficient $coeff_i$  là: 

$$coeff_0 * unknown_{b0}^2 * unknown_{c0}^3 + coeff_0 * unknown_{b1}^2 * unknown_{c1}^3 + coeff_0 * unknown_{b2}^2 * unknown_{c2}^3 + ... + coeff_0 * unknown_{b9}^2 * unknown_{c9}^3 + sum(aa_0) = output_0 $$

Với $b, c \in [0,9]$

Ta sẽ dựng ma trận như sau:


$$
\begin{equation*}
    \begin{bmatrix}
        coeff_0.b_0.c_0 & coeff_0.b_0.c_1 & ... & coeff_0.b_9.c_9 \\
        coeff_1.b_0.c_0 & coeff_1.b_0.c_1 & ... & coeff_1.b_9.c_9 \\
        \vdots & \vdots & & \vdots  \\
    coeff_{99}.b_0.c_0 & coeff_{99}.b_0.c_1 & ... & coeff_{99}.b_9.c_9 \\
    \end{bmatrix}
    =
    \begin{bmatrix}
         output_0 - sum(aa_0) \\
        output_1 - sum(aa_1) \\
        \vdots  \\
        output_{99} - sum(aa_{99})
    \end{bmatrix}
\end{equation*}
$$

Giải ma trận bằng sage ta thu được 100 nghiệm của  $unknown_0^2 * unknown_0^3$ đến $unknown_9^2 * unknown_9^3$

Thu được 10 giá trị của $unknown_0^5$ đến $unknown_9^5$ ta chỉ cần  căn bậc 5 của $unknown$ và chia dư cho 1000 để khôi phục lại flag.

``solved.py``
```python
from sage.all import *

p = ...
output = ...

m = []
y = []
vars = [[i, j] for i in range(10) for j in range(10)]

for i in range(0, len(output), 4):
    aa = output[i]
    bb = output[i+1]
    cc = output[i+2]
    rs = output[i+3]
    
    coeffs = [0]*100

    # sum([aa[i] + unknowns[bb[i]]^2 + unknowns[cc[i]]^3 for i in range(1000)])
    sum = 0

    for j in range(1000):
        sum = (sum + aa[j]) % p
        temp = [bb[j], cc[j]]
        coeffs[vars.index(temp)] += 1

    y.append((rs-sum) % p)
    row = coeffs
    m.append(row)

l = len(m)
m = Matrix(GF(p), m)
y = vector(GF(p), y)

ans = list(m.solve_right(y))
print(ans)

flag = []
for i in range(100):
        u = gmpy2.iroot(int(ans[i]), 5)
        print(u)
        if u[1] == True:
            u = u[0] % 1000
            flag.append(u)
            print(u)
            print(flag)
print("".join([bytes([c]).decode() for c in flag]))
```

### challengename

``chall.py``
```python
from ecdsa.ecdsa import Public_key, Private_key
from ecdsa import ellipticcurve
from hashlib import md5
import random
import os
import json

flag = open("flag", "rb").read()[:-1]

magic = os.urandom(16)

p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
a = ###REDACTED###
b = ###REDACTED###
G = ###REDACTED###

q = G.order()

def bigsur(a,b):
    a,b = [[a,b],[b,a]][len(a) < len(b)]
    return bytes([i ^ j for i,j in zip(a,bytes([int(bin(int(b.hex(),16))[2:].zfill(len(f'{int(a.hex(), 16):b}'))[:len(a) - len(b)] + bin(int(b.hex(),16))[2:].zfill(len(bin(int(a.hex(), 16))[2:]))[:len(bin(int(a.hex(), 16))[2:]) - len(bin(int(b.hex(), 16))[2:])][i:i+8], 2) for i in range(0,len(bin(int(a.hex(), 16))[2:]) - len(bin(int(b.hex(), 16))[2:]),8)]) + b)])

def bytes_to_long(s):
    return int.from_bytes(s, 'big')

def genkeys():
    d = random.randint(1,q-1)
    pubkey = Public_key(G, d*G)
    return pubkey, Private_key(pubkey, d)

def sign(msg,nonce,privkey):
    hsh = md5(msg).digest()
    nunce = md5(bigsur(nonce,magic)).digest()
    sig = privkey.sign(bytes_to_long(hsh), bytes_to_long(nunce))
    return json.dumps({"msg": msg.hex(), "r": hex(sig.r), "s": hex(sig.s)})

def enc(privkey):
    x = int(flag.hex(),16)
    y = pow((x**3 + a*x + b) % p, (p+3)//4, p)
    F = ellipticcurve.Point('--REDACTED--', x, y)
    Q = F * privkey.secret_multiplier
    return (int(Q.x()), int(Q.y()))

pubkey, privkey = genkeys()
print("Public key:",(int(pubkey.point.x()),int(pubkey.point.y())))
print("Encrypted flag:",enc(privkey))

# Public key: (99122053878685444817852582103585646482441799670468212049632161370423019963573, 49681263796445807694244738028189208770171168855624587289690892802435841601423)
# Encrypted flag: (22455982735997721923198309515515820680837002550923840212531066823876108860098, 49955453626898315794129063911602706078234097783588068635922441060010795905908)

nonces = set()

for _ in '01':
    try:
        msg = bytes.fromhex(input("Message: "))
        nonce = bytes.fromhex(input("Nonce: "))
        if nonce in nonces:
            print("Nonce already used")
            continue
        nonces.add(nonce)
        print(sign(msg,nonce,privkey))
    except ValueError:
        print("No hex?")
        exit()
```

Chall này tôi nhận được 2 điểm từ đường cong: Public key là **dG** và Encrypt flag là **dF**

Từ 2 điểm trên ta có 

$$y_1^2 = x_1^3 + ax_1 + b$$ 

$$y_2^2 = x_2^3 + ax_2 + b$$ 

$$(y_1^2 - y_2^2) - (x_1^3 - x_2^3) = a(x_1-x_2)$$

Ta có a, b được tính bằng

```
sage: p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
....: x1 = 5683931425003308547431366441507256115275884385439908235960128031931809224426
....: y1 = 103504881232349341101391567415775837049449360982146318680463741565720272773736
....: x2=  87512180974071789579460785103236717308728056532898014966250203004749159040100
....: y2 = 5463487042701233117805115066761912607366330124308643383693672326808102345649
....: a=Mod(((y1^2 - y2^2)-(x1^3 - x2^3))*inverse_mod((x1-x2),p),p)
....: b=Mod(y2^2 - x2^3 - a*x2, p)
....: a, b
(115792089210356248762697446949407573530086143415290314195533631308867097853948,
 41058363725152142129326129780047268409114441015993725554835256314039467401291)
sage:
```

Ta có curve của bài
```
sage: p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
....: a = 115792089210356248762697446949407573530086143415290314195533631308867097853948
....: b =  41058363725152142129326129780047268409114441015993725554835256314039467401291
....: E = EllipticCurve(GF(p), [a, b])
....: E.order()
115792089210356248762697446949407573529996955224135760342422259061068512044369
sage:
```

Ở đây hàm **bigsur** trông rất dài nhưng thực chất nó là phép xor. Nếu chúng ta chọ **nonce1 = b"\x00"** và **nonce2 = b"\x00\x00"** thì sẽ có thể lấy cùng một **nunce** trong hàm **sign()** mà server ký message

Để tìm lại private key **d** tôi sử dụng [️ECDSA Nonce Reuse Attack](https://crypto.stackexchange.com/questions/71764/is-it-safe-to-reuse-a-ecdsa-nonce-for-two-signatures-if-the-public-keys-are-diff)

Chúng ta có **r1 = r2 = R**, **(r1, s1)** là chữ ký của $m_1$, **(r2, s2)** là chữ ký của $m_2$ khi đó chúng ta có 

$$s_1 * k - H(m_1) = s_2 * H(m_2) = R * privatekey$$ 

$$k = \frac{s_1 - s_2}{H(m_1) - H(m_2)}$$

$$privatekey = \frac{s_1 * k - H(m_1)}{R}$$

Khi có **privatekey** ta dễ dàng tìm lại **F** bằng phép tính $F = private^
{-1} * Q$

``solved.sage``
```python
from hashlib import md5
from Crypto.Util.number import *
from sage.all import *

def inverse_mod(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        raise Exception('Modular inverse does not exist')
    else:
        return x % m

def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
    else:
        g, y, x = egcd(b % a, a)
        return (g, x - (b // a) * y, y)

def recover_private_key(H_m1, H_m2, r, s1, s2, q):
    H_m1_int = bytes_to_long(md5(H_m1).digest())
    H_m2_int = bytes_to_long(md5(H_m2).digest())
    r_inv = inverse_mod(r, q)
    d = ((inverse_mod(s1 - s2, q) * (H_m1_int * s2 - H_m2_int * s1) % q) * r_inv) % q
    return d

        
# vanluongkma@Nitro:~$ nc 13.201.224.182 30773
x1, y2 = (5683931425003308547431366441507256115275884385439908235960128031931809224426, 103504881232349341101391567415775837049449360982146318680463741565720272773736)
x2, y2 =  (87512180974071789579460785103236717308728056532898014966250203004749159040100, 5463487042701233117805115066761912607366330124308643383693672326808102345649)
# Message: 4b435343
# Nonce: 00
# {"msg": "4b435343", "r": "0x634c264ed704268912a6770587a38659be6b14c02276b7b8a357663aa4b807e", "s": "0xee0223546119ce0300f129d9b3df224805b58eb2d6974ff43f6ba9cad1b774cb"}
# Message: 4b435343435446
# Nonce: 0000
# {"msg": "4b435343435446", "r": "0x634c264ed704268912a6770587a38659be6b14c02276b7b8a357663aa4b807e", "s": "0xab628f493d21b15f1ae5626f3c08e6533942ffd558d62d9f3470765119ab4b04"}
p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
a = 115792089210356248762697446949407573530086143415290314195533631308867097853948
b = 41058363725152142129326129780047268409114441015993725554835256314039467401291
E = EllipticCurve(GF(p), [a, b])
G = E(0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296, 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5)
E.set_order(0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 * 0x1)
n = int(E.order())

H_m1 = b"KCSC"
H_m2 = b"KCSCCTF"

r = 0x634c264ed704268912a6770587a38659be6b14c02276b7b8a357663aa4b807e  
s1 = 0xee0223546119ce0300f129d9b3df224805b58eb2d6974ff43f6ba9cad1b774cb 
s2 = 0xab628f493d21b15f1ae5626f3c08e6533942ffd558d62d9f3470765119ab4b04 
q = int(E.order())


d = recover_private_key(H_m1, H_m2, r, s1, s2, q)
print(d)

d_inverse=inverse_mod(d,n)
Q = E(x2, y2)
F = d_inverse*Q
print(long_to_bytes(int(F[0])))
```

### rr

``chall.py``
```python
from Crypto.Util.number import *
from FLAG import flag
from random import randint

flag = bytes_to_long(flag)
n = ..
rr = ..
ks = [randint(0, rr**(i+1)) for i in range(20)]
c1 = pow(sum(k*flag**i for i, k in enumerate(ks)), (1<<7)-1, n)
c2 = pow(flag, (1<<16)+1, n)
ks = [pow(69, k, rr**(i+2)) for i, k in enumerate(ks)]
print(f"{ks = }")
print(f"{c1 = }")
print(f"{c2 = }")
```

Challenge cho chúng ta 2 bản mã $c_1, c_2$ của **flag**

$$
\begin{cases}
   c_1 = (\sum_{i=1}^{20} k_i flag^i)^{127} \mod n \\
   c_2 = flag^{65537} \mod n \\
\end{cases}
$$


Những hệ số $k_1, k_2, k_3, ... , k_{20}$ đã biết, chúng ta cần xây dựng đa thức $$f(x) = (\sum_{i=1}^{20} k_i x^i)^{127} - c_1$$ $$g(x) = x^{65537} - c_2$$
 
Chúng ta có thể tìm GCD của 2 đa thức **f(x), g(x)** để recover lại flag 

Nhưng mà hệ số $k_i$ cũng đã được mã hóa theo phương trình sau:

$$K_i = 69 ^ {k_i} \mod p^{i+1}, k_i < p^{i}, i \in [1...20]$$

Với i = 0

$$K_1 = 69^{k_1} \mod p^2, k_1 < p$$

Nâng  cả 2 vế lũy thừa **q**

$$K_1^q = (69^q)^{k_1} \mod p^2$$

Với định lý fermat nhỏ( $a^{\phi(p)} = p * k + 1$) ta có :

$$\underbrace{(c_1 * p + 1)}_{K_1^q \mod p^2} = {\underbrace{(d_1 * p + 1)}_{69^q \mod p^2}}^{k_1} \mod p^2$$

Khai triển [Binomial theorem](https://en.wikipedia.org/wiki/Binomial_theorem) cho vế phải ta có

$$c_1 * p  + 1 = \binom{k_1}{0} 1^n (d_1 * p)^0 + \binom{k_1}{1}1^{n-1}(d_1p)^1 \underbrace{ + \cdots }_{0 \mod p^2} \mod p^2$$

Thu được

$$c_1p + 1 = 1 + d_1k_1p \mod p^2$$

$$\implies c_1 = d_1 * k_1 \mod p$$

$$\implies k_1 = c_1 * d_1 ^ {-1} \mod p$$

Ta đã giải quyết được discrete logarithm trên $Z_{p^2}$ tiếp theo là $Z_{p^r}$

$$y = g^x \mod p^{r+2}, \ x < p^{(r+1)}$$

Đặt $x = x_r * p^r + ... + x_1 * p^1 + x_0 = p - 1$ ta khai triển như sau

$$y^q = (g^q)^x \mod p^{r+2}$$

Tương tự như trên thu được

$$=> c_{1} = d_1 * x{1} \mod \ p$$

$$=> x_{1} = c_{1} * d_{1}^{-1} \mod \ p$$

Như vậy ta đã recover được $x_0$, biến đổi phương trình như sau

$$y^q = (g^q)^x \mod p^{r+2}$$

$$y^q = (g^q)^{({x_{r} \ p^{r} + … + x_{1} \ p + x_{0}})} \mod p^{r+2}$$

$$(y \ g^{-x_{0}})^q = (g^q)^{p \ ({x_{r} \ p^{r} + … + x_{2} \ p + x_{1}})} \mod p^{r+2}$$

đặt: $y’ = (y \ g^{-x_{0}})$ và $g’ = g^p$

Tương tự như trên ta sẽ thu được $x_1$. Lặp lại đến khi $g^{p^{r+1} \ q} = 1 \mod p^{r+2}$, ta sẽ thu được x hoàn chỉnh.

``solved_1.py``
```python
from data import ks, c1, c2, rr, n
from Crypto.Util.number import *

def binomial_dlog_sub(y,g,p,q,r=2):
    C = ZZ(pow(y, q, p^r))
    B = ZZ(pow(g, q, p^r))
    kx = (C - 1)//p^(r-1)
    k =  (B - 1)//p^(r-1)
    x = ZZ(kx * pow(k, -1, p) % p)
    return x

def binomial_dlog(y,g,p,q,r):
    assert r >= 2
    y = ZZ(y)
    g = ZZ(g)
    xs = []
    for i in range(r-1):
        xi = binomial_dlog_sub(y, g, p, q, i + 2)
        xs.append(xi)
        y = ZZ(y * pow(g,-xi,p^r) % p^r)
        g = ZZ(pow(g,p,p^r))
    return ZZ(xs, p)
    
def pgcd(g1, g2):
    while g2:
        g1, g2 = g2, g1 % g2
    return g1.monic()

p = rr
q = rr - 1

coeffs = []
g  = 69
for rd in range(20):
    y = ks[rd]
    r = rd + 2
    x = binomial_dlog(y,g,p,q,r)
    print(f"[+] rounds {rd} check ", ZZ(pow(g, x, p^r)) == y)
    coeffs.append(x)

PR.<x> = PolynomialRing(Zmod(n))
f1 = (sum(k*x**i for i, k in enumerate(coeffs)))^((1<<7)-1) - c1
f2 = x^(65537) - c2
f0 = pgcd(f1,f2)
print(long_to_bytes(int(- f0.constant_coefficient() % n)))
```

Cách khác ta sẽ recover lại $k_i$ bằng bài toán trên trường số p-adic

``solved_2.py``
```python
import sys
sys.path.append("/mnt/c/Users/dinhv/Documents/Tool/crypto-attacks/")
from sage.all import *
from data import c1,c2,ks,n,rr
from Crypto.Util.number import *
from shared.polynomial import fast_polynomial_gcd

def dlog_on_Zp(p, exponent_of_p, g, y):
    Z = Zp(p, prec=exponent_of_p)
    x_Z = Z(y).log() / Z(g).log()
    x = x_Z.lift()
    return x

sol = []
for i,k in enumerate(ks):
    tmp = dlog_on_Zp(rr, i+2, 69, k)
    sol.append(tmp)
    print("Found", tmp)

P = PolynomialRing(Zmod(n), names='x')
(x,) = P.gens()

g1 = sum(k*x**i for i, k in enumerate(sol))**((1<<7)-1) - c1
g2 = x**65537 - c2
f = fast_polynomial_gcd(g1,g2)
print(long_to_bytes(int(f.small_roots()[0])))
```
#### Reference

[1] [crypto-attack](https://github.com/jvdsn/crypto-attacks/blob/master/shared/polynomial.py)

[2] Thanks [nomorecaffeine](https://github.com/nguyen-xuan-quoc)

[3] Thanks [tl2cents](https://tanglee.top/2024/02/27/bi0sCTF-2024-Crypto-Writeups/)

### daisy_bel

``chall.py``
```python
from Crypto.Util.number import *
from FLAG import flag

p = getPrime(1024)
q = getPrime(1024)
n = p*q
c = pow(bytes_to_long(flag), 65537, n)

print(f"{n = }")
print(f"{c = }")
print(f"{p>>545 = }")
print(f"{pow(q, -1, p) % 2**955 = }")

"""
n = 13588728652719624755959883276683763133718032506385075564663911572182122683301137849695983901955409352570565954387309667773401321714456342417045969608223003274884588192404087467681912193490842964059556524020070120310323930195454952260589778875740130941386109889075203869687321923491643408665507068588775784988078288075734265698139186318796736818313573197531378070122258446846208696332202140441601055183195303569747035132295102566133393090514109468599210157777972423137199252708312341156832737997619441957665736148319038440282486060886586224131974679312528053652031230440066166198113855072834035367567388441662394921517
c = 7060838742565811829053558838657804279560845154018091084158194272242803343929257245220709122923033772911542382343773476464462744720309804214665483545776864536554160598105614284148492704321209780195710704395654076907393829026429576058565918764797151566768444714762765178980092544794628672937881382544636805227077720169176946129920142293086900071813356620614543192022828873063643117868270870962617888384354361974190741650616048081060091900625145189833527870538922263654770794491259583457490475874562534779132633901804342550348074225239826562480855270209799871618945586788242205776542517623475113537574232969491066289349
p>>545 = 914008410449727213564879221428424249291351166169082040257173225209300987827116859791069006794049057028194309080727806930559540622366140212043574
pow(q, -1, p) % 2**955 = 233711553660002890828408402929574055694919789676036615130193612611783600781851865414087175789069599573385415793271613481055557735270487304894489126945877209821010875514064660591650207399293638328583774864637538897214896592130226433845320032466980448406433179399820207629371214346685408858
"""
```


Với bài này ta có 

$$u=q^{-1} mod p$$ 

$$uq = 1 + kp$$ 

$$uq^2 = q + kpq$$ 

$$uq^2 - q = 0 mod n$$


Chúng ta có 545 high bits của p ($p_h$) và 955 low bits của ($q_l^{-1}$) = $q^{-1} mod (p)$ 

Từ thông tin trên chúng ta có $f(x,y) = (u_h * 2^{955} + u_l) * (q_h * 2^{545} + q_l)^2 - (q_h * 2^{545} + q_l) = 0 \ mod \ (n)$

Chúng ta sẽ sử dụng thuật toán coppersmith để tìm ($q_l, u_h$)

Ở đây ta sẽ dùng [**coppersmith multivariate heuristic**](https://github.com/kionactf/coppersmith/blob/main/coppersmith_multivariate_heuristic.py) để giải quyết bài toán này.

``solved.sage``
```python
from coppersmith_multivariate_heuristic import coppersmith_multivariate_heuristic
from lll import *
from sage.all import *
from Crypto.Util.number import *
import sys
sys.path.append("/coppersmith")


n = 13588728652719624755959883276683763133718032506385075564663911572182122683301137849695983901955409352570565954387309667773401321714456342417045969608223003274884588192404087467681912193490842964059556524020070120310323930195454952260589778875740130941386109889075203869687321923491643408665507068588775784988078288075734265698139186318796736818313573197531378070122258446846208696332202140441601055183195303569747035132295102566133393090514109468599210157777972423137199252708312341156832737997619441957665736148319038440282486060886586224131974679312528053652031230440066166198113855072834035367567388441662394921517
c = 7060838742565811829053558838657804279560845154018091084158194272242803343929257245220709122923033772911542382343773476464462744720309804214665483545776864536554160598105614284148492704321209780195710704395654076907393829026429576058565918764797151566768444714762765178980092544794628672937881382544636805227077720169176946129920142293086900071813356620614543192022828873063643117868270870962617888384354361974190741650616048081060091900625145189833527870538922263654770794491259583457490475874562534779132633901804342550348074225239826562480855270209799871618945586788242205776542517623475113537574232969491066289349

msb_p = 914008410449727213564879221428424249291351166169082040257173225209300987827116859791069006794049057028194309080727806930559540622366140212043574
lsb_u = 233711553660002890828408402929574055694919789676036615130193612611783600781851865414087175789069599573385415793271613481055557735270487304894489126945877209821010875514064660591650207399293638328583774864637538897214896592130226433845320032466980448406433179399820207629371214346685408858
msb_q = (n // (msb_p << 545))

x, y = PolynomialRing(Zmod(n), "x, y").gens()

f = (y * 2**955 + lsb_u) * (msb_q + x)**2 + (msb_q + x)

ans = coppersmith_multivariate_heuristic(f, [2**545, 2**69], beta = 1)
lsb_q, msb_u = ans[0]

qq = msb_q + x
q = int(qq(lsb_q))
p = n // q
d = pow(65537, -1, (p - 1) * (q - 1))
m = pow(c, d, n)
print(long_to_bytes(m))
```