Crypto 雜題選做

APJifengc發表於2024-07-31

?apj 你在幹神魔

目錄
  • W4terCTF 2024
    • Merciful ZMJ4396
    • d3
  • Google CTF 2023
    • LEAST COMMON GENOMINATOR
  • DeadSec CTF 2024
    • Raul Rosas
  • corCTF 2024
    • steps
    • monkfish / anglerfish

W4terCTF 2024

之前朋友給我看的題

Merciful ZMJ4396

找不到原來的 task.py 了,記得大概是這麼個題,口胡一下吧

有兩個多項式 \(F(m) = m^{43} + 96, G(m) = (m-7)^{77} + (m+777)^7\),要你找到一個 \(m\),使得 \(\gcd(F(m), G(m)) > 2^{7777}\)

當時沒做出來,後來看 wp 才大概看明白的

首先關於多項式 \(\gcd\) 有一些高深理論,我沒太研究明白,反正大概就是有個東西叫結式(resultant),然後兩個多項式的值的 \(\gcd\) 一定是這兩個多項式的結式的因子,且結式的每個因子都一定可以被取到。好像透過一些對多項式做 exgcd 也能得到這個數?確實沒有太研究明白就擺了,丟個 wikipedia 在這裡吧。還有個關於 \(\gcd\) 值的 ref

總而言之,對於這題來說,可以直接求一下 \(F(m)\)\(G(m)\) 的結式,就能知道這兩個多項式 \(\gcd\) 的最大值是多少了。設它為 \(P\),級別大概是 \(2^{9000}\) 多的。那麼現在我們只需要找到能夠取到它的 \(m\) 即可。當然這好像還是不太容易,因為這等價於解 \(F(m) \equiv 0 \pmod P\)\(G(m) \equiv 0 \pmod P\) 的解集的交,但是光求一個高次方程的解已經不可接受了(\(P\) 太大了無法分解,所以很難做高次剩餘),所以我們還需要考慮一些東西。

實際上我們可以找一下在模 \(P\) 意義下的 \(\gcd(F, G)\),這裡是多項式的 \(\gcd\)。那麼我們要的解就是 \(\gcd(F, G) \equiv 0 \pmod P\) 了。(我怎麼沒想到這個)但是發現模合數 \(\gcd\) 好像不一定存在,庫直接沒有這個的實現,那自己寫一個樸素 \(\gcd\) 吧。發現確實找不到 \(\gcd\),因為沒有逆元。嘗試將 \(P\) 的比較小的因子除掉(\(2^{10} | P\)),然後發現就找到 \(\gcd\) 了,而且次數很小,是個一次多項式。這樣直接就解出 \(m\) 了。

from sage.all import *
from gmpy2 import *

def gcd1(a, b):
    if b == 0:
        return a.monic()
    else:
        return gcd1(b, a % b)

PR = PolynomialRing(ZZ,'x')
x = PR.gen()
f = x ** 43 + 96
g = (x - 7) ** 77 + (x + 777) ** 7
P = abs(f.resultant(g))
print(P)
P //= 2 ** 10

PR = PolynomialRing(Zmod(P),'x')
x = PR.gen()
f = x ** 43 + 96
g = (x - 7) ** 77 + (x + 777) ** 7
h = gcd1(f, g)
print(h)
# h's degree = 1
m = gmpy2.mpz(P - h[0])
ans = gmpy2.gcd(m ** 43 + 96, (m - 7) ** 77 + (m + 777) ** 7)

print(ans.bit_length())

print(m)

d3

好像是叫這個???我忘了,同樣是找不到 task.py,資料範圍也不咋記得了,直介面胡了,我只記得當時我做法是對的但是有些細節弱智了導致掉了大量精度,結果最後跑不出來答案

給你一個數 \(d\) 的立方根的小數部分,還原 \(d\)

小數部分大概是給到了 \(2^{-1024}\) 的精度?記不太清了。

設整數部分為 \(x\),小數部分為 \(y\),那麼 \(\sqrt[3]{d} = x + y\),我們知道 \(y\),現在相當於要找 \(x\)

我們知道的資訊應當是 \((x + y)^3 \equiv 0 \pmod 1\),拆一下有 \(x^3 + 3x^2y + 3xy^2 + y^3 \equiv 0 \pmod 1\)。注意到 \(x\) 是整數,所以實際上限制是 \(3x^2y + 3xy^2 + y^3 \equiv 0 \pmod 1\)\(y^3\) 是一個已知數,我們現在相當於要解一個關於 \(x\) 的二次方程。但是當然要注意到我們這裡的精度是有限的,所以我們實際上要做的是找到一個近似解。

首先把浮點數幹掉,我們左右同時乘上一個大數 \(A\),大概 \(2^{2048}\),取整一下,然後就可以得到一個整數的問題了。(有個傻逼先把 \(y\) 取整之後再求三次方,然後精度炸的不剩了,不說是誰)現在問題就是求 \(Px^2 + Qx + R \equiv 0 \pmod A\) 了。注意我們可以大概預估 \(x\) 的大小應該在 \(2^{300}\) 級別。這個二次很火大啊,我們考慮直接把 \(x^2\)\(x\) 獨立開,令 \(z = x^2\),然後假設 \(z\) 是一個 \(2^{600}\) 級別的變數,直接跑跑看。然後這就是一個標準形式的線性同餘了,可以直接上格基 LLL 了。不會的可以看看 WC 2024 遊記

我手頭沒程式碼,懶得寫了!

Google CTF 2023

LEAST COMMON GENOMINATOR

ok 這題是當時閒的沒事做的。來試試自己能不能做出來 google ctf 的 crypto 簽到題(

但是好像也錯過了 google ctf 2024,哈哈

task.py

題目大概是給了一個 LCG,你不知道它的係數,然後給出了前 6 個生成的數,然後用這個 LCG 生成了 RSA 的 key,我們的目標就是透過這 6 個生成的數還原出來這個 LCG 的係數。

LCG:\(x_i = (x_{i-1} \cdot m + c) \bmod n\)

給出了 \(x_1 \sim x_6\),我們可以先差分一下,\(x_{i+1} - x_{i} = m(x_{i} - x_{i-1})\),令 \(y_i = x_{i+1} - x_i\),那麼 \(y_i\) 實際上是形成了一個等比數列,\(y_i = y_{i-1} \cdot m\)。但是我們現在即不知道比也不知道模數。

考慮相鄰兩組,假設為 \(y_3 = y_2 m + t_2 n, y_2 = y_1 m + t_1 n\),可以將 \(m\) 消去,得到 \(y_3 y_1 - t_2 y_1 n = y_1 y_2 m = y_2^2 - t_1 y_2 n\),那麼就有 \((t_1 y_2 - t_2 y_1) n = y_2^2 - y_3 y_1\),這實際上告訴了我們 \(n\) 一定是 \(y_2^2 - y_3 y_1\) 的因子,而我們有 4 組這樣的方程,將所有的 \(y_2^2 - y_3 y_1\) 求一下 \(\gcd\) 就能還原出來 \(n\) 了,然後就容易解出整個 LCG 了。

程式碼還是沒有,太久之前做的題了現在沒有程式碼了!

DeadSec CTF 2024

ok 這個是最近打的了。

img

有點菜!crypto 被卡了兩題,雖然最後看題解發現是傻逼題,兩題我想法都是對的但是一個我算的理論不可行一個我跑到一半放棄了。那兩個題不寫了,感覺真沒意思。

Raul Rosas

task.py:

from Crypto.Util.number import * 
from sympy import nextprime

p1 = bin(getPrime(1024))[2:]
p2 = p1[:605]
p2 = p2 + ('0'*(len(p1)-len(p2)))

p1 = int(p1,2)
p2 = nextprime(int(p2,2))

q1 = getPrime(300)
q2 = getPrime(300)

n1 = p1*p1*q1 
n2 = p2*p2*q2 

e = 65537 
flag = bytes_to_long(b'REDACTED')
c1 = pow(flag,e,n1)
c2 = pow(flag,e,n2)

print(f'{n1=}')
print(f'{n2=}')
print(f'{c1=}')
print(f'{c2=}')

這個題對於 OIer 還是不太難的吧!雖然我做法可能有點偏,正解可能不是這個(

程式碼裡 \(p_1\) 是一個隨機數,\(p_2\) 是一個和 \(p_1\) 很接近的質數,\(q_1, q_2\) 是兩個隨機質數。然後給出了同一個 flag 的兩次 RSA 加密,模數 \(n_1 = p_1^2 q_1, n_2 = p_2^2 q_2\)。由於我們知道 \(p_1\)\(p_2\) 相差很小,那麼 \(\frac{n_1}{n_2}\) 約等於 \(\frac{q_1}{q_2}\),而且我們知道 \(q_1, q_2\) 的位數,我們直接找到一個 \(\frac{n_1}{n_2}\) 的最近有理逼近即可。我寫的是 Stern-Broot 樹上樸素二分,比較簡單。

from Crypto.Util.number import * 
from sympy import nextprime
from decimal import *
getcontext().prec = 1024

n1 = 33914684861748025775039281034732118800210172226202865626649257734640860626122496857824722482435571212266837521062975265470108636677204118801674455876175256919094583111702086440374440069720564836535455468886946320281180036997133848753476194808776154286740338853149382219104098930424628379244203425638143586895732678175237573473771798480275214400819978317207532566320561087373402673942574292313462136068626729114505686759701305592972367260477978324301469299251420212283758756993372112866755859599750559165005003201133841030574381795101573167606659158769490361449603797836102692182242091338045317594471059984757228202609971840405638858696334676026230362235521239830379389872765912383844262135900613776738814453
n2 = 45676791074605066998943099103364315794006332282441283064976666268034083630735700946472676852534025506807314001461603559827433723291528233236210007601454376876234611894686433890588598497194981540553814858726066215204034517808726230108550384400665772370055344973309767254730566845236167460471232855535131280959838577294392570538301153645042892860893604629926657287846345355440026453883519493151299226289819375073507978835796436834205595029397133882344120359631326071197504087811348353107585352525436957117561997040934067881585416375733220284897170841715716721313708208669285280362958902914780961119036511592607473063247721427765849962400322051875888323638189434117452309193654141881914639294164650898861297303
c1 = 5901547799381070840359392038174495588170513247847714273595411167296183629412915012222227027356430642556122066895371444948863326101566394976530551223412292667644441453331065752759544619792554573114517925105448879969399346787436142706971884168511458472259984991259195488997495087540800463362289424481986635322685691583804462882482621269852340750338483349943910768394808039522826196641550659069967791745064008046300108627004744686494254057929843770761235779923141642086541365488201157760211440185514437408144860842733403640608261720306139244013974182714767738134497204545868435961883422098094282377180143072849852529146164709312766146939608395412424617384059645917698095750364523710239164016515753752257367489
c2 = 3390569979784056878736266202871557824004856366694719533085092616630555208111973443587439052592998102055488632207160968490605754861061546019836966349190018267098889823086718042220586285728994179393183870155266933282043334755304139243271973119125463775794806745935480171168951943663617953860813929121178431737477240925668994665543833309966378218572247768170043609879504955562993281112055931542971553613629203301798161781786253559679002805820092716314906043601765180455118897800232982799905604384587625502913096329061269176369601390578862509347479694697409545495592160695530037113884443071693090949908858172105089597051790694863761129626857737468493438459158669342430468741236573321658187309329276080990875017

e = 65537

# find approximation of n1/n2

l = (0, 1)
r = (1, 0)

def check(q1, q2):
    if q1 != 1 and n1 % q1 == 0 and n2 % q2 == 0:
        print(q1, q2)
        p2 = int(Decimal(n2 // q2).sqrt())
        d = inverse(e, (p2 - 1) * p2 * (q2 - 1))
        m = pow(c2, d, n2)
        print(long_to_bytes(m))

while l[1] < 2 ** 300 and r[1] < 2 ** 300:
    mid = (l[0] + r[0], l[1] + r[1])
    check(mid[0], mid[1])
    if mid[0] * n2 < n1 * mid[1]:
        l = mid
    else:
        r = mid

corCTF 2024

這個是在上一場 ctf 被那兩題卡自閉之後棄賽來打的這場。

img

彩筆隊友這場 pwn 和 web 一題沒過,要不然還能再多點分)

steps

task.py:

from Crypto.Util.number import getPrime
from random import randint
from hashlib import sha512
from secret import FLAG

p = getPrime(1024)

def apply(x, y):
    z0 = x[0] * y[1] + x[1] * y[0] - x[0] * y[0]
    z1 = x[0] * y[0] + x[1] * y[1]
    return z0 % p, z1 % p

def calculate(n):
    out = 0, 1
    base = 1, 1

    while n > 0:
        if n & 1 == 1: out = apply(out, base)
        n >>= 1
        base = apply(base, base)

    return out

def step(x, n):
    '''Performs n steps to x.'''
    return apply(x, calculate(n))

def xor(a, b):
    return bytes(i ^ j for i, j in zip(a, b))

g = tuple(randint(0, p - 1) for _ in range(2))
a = randint(0, p)
b = randint(0, p)

A = step(g, a)
B = step(g, b)

print(p)
print(g)
print(A)
print(B)

shared = step(A, b)
assert shared == step(B, a)

pad = sha512(str(shared).encode()).digest()
print(xor(FLAG, pad))

注意到 他實際上寫了個斐波那契數列,,,反正把轉移矩陣寫出來發現就是個斐波那契

那麼實際上是給出了 \(g A^n\)\(g A^m\),可以直接解方程解出來 \(f_n, f_{n-1}\)\(f_m, f_{m-1}\),然後求 \(f_{n+m}\) 即可,可以用一下 \(f_{n+m} = f_n f_{m+1} + f_{n-1} f_{m}\),不太重要,隨便算一下。

monkfish / anglerfish

task.py 太長了,不貼了

沒看懂這題想幹啥,,,有點搞笑,還分了兩題

題目裡給出了一個看起來很複雜的線性代數的演算法,但是實際上都不重要,因為我們關注一下 \(verify(v, F, pok = (com, resp, verif))\) 函式,大概是用輸入的 \((com, v, verif)\) 三元組為隨機數種子隨機一個 \(a\),然後判斷 \(apply(F, resp) = com + a^2 v - a \cdot verif\)。但是所有數都是 \(\bmod 5\),意味著實際上 \(a\) 是可能隨機到 \(0\) 的,而如果 \(a = 0\) 的時候這個判斷條件是很容易滿足的,只需要 \(com = apply(F, resp)\),而 \(com, resp\) 都是我們輸入的,只需要隨機一個 \(resp\) 並確定對應的 \(com\),為了隨機到 \(a=0\) 我們一直對 \(verif\) 進行隨機就行了。

#!/usr/bin/sage

import sys
print("I caught a monkfish in the sea! ")
sys.stdout.flush()

from hashlib import sha256
from Crypto.Util.number import bytes_to_long
from random import SystemRandom
import ast

n = 100
m = 100
q = 5
FF.<x> = GF(q)


def apply(F, v):
    out = []
    for i in range(m):
        out.append((v.T * F[i] * v)[0, 0])
    return matrix(FF, m, 1, out)

def apply_verif_info(F, a, b):
    out = []
    for i in range(m):
        out.append((a.T * (F[i] + F[i].T) * b)[0, 0])
    return matrix(FF, m, 1, out)

def create_pok(v, s, F):
    t = matrix(FF, n, 1, [FF.random_element() for i in range(n)])
    com = apply(F, t)
    verif = apply_verif_info(F, t, s)
    a = list(FF)[sha256(bytes([list(FF).index(i[0]) for i in list(com) + list(v) + list(verif)])).digest()[0] % len(list(FF))]
    print("a =", a)
    return (com, t - a * s, verif)

def verif_pok(v, F, pi):
    com = pi[0]
    resp = pi[1]
    verif = pi[2]
    a = list(FF)[sha256(bytes([list(FF).index(i[0]) for i in list(com) + list(v) + list(verif)])).digest()[0] % len(list(FF))]
    out1 = apply(F, resp)
    out2 = com + (a * a) * v - a * verif
    return out1 == out2

gen_seed = bytes([83, 134, 8, 60, 109, 129, 153, 246, 112, 132, 154, 0, 129, 173, 49, 229, 71, 79, 145, 91, 146, 44, 34, 251, 95, 41, 13, 248, 24, 126, 215, 95, 208, 88, 24, 74, 224, 166, 19, 232, 254, 0, 142, 215, 146, 93, 87, 249, 239, 253, 137, 92, 124, 201, 164, 4, 133, 176, 76, 70, 166, 193, 68, 148])

F = []

for i in range(m):
    cur = []
    for j in range(n):
        cur.append([])
        for k in range(n):
            cur[-1].append(list(FF)[sha256(gen_seed).digest()[0] % len(list(FF))])
            gen_seed = sha256(gen_seed).digest()
    F.append(matrix(FF, n, n, cur))

vl = [2, 0, 3, 2, 4, 2, 4, 1, 4, 3, 4, 1, 1, 1, 2, 0, 4, 3, 4, 0, 0, 0, 0, 3, 2, 2, 3, 2, 0, 1, 1, 4, 2, 3, 4, 2, 4, 4, 2, 0, 1, 0, 1, 3, 4, 0, 0, 1, 0, 3, 4, 3, 0, 3, 4, 1, 1, 4, 1, 3, 0, 1, 4, 1, 2, 2, 2, 3, 2, 2, 4, 2, 4, 3, 0, 0, 3, 1, 4, 2, 1, 2, 1, 3, 2, 3, 4, 4, 4, 0, 1, 1, 2, 2, 1, 4, 3, 0, 2, 4]

v = matrix(FF, n, 1, [list(FF)[i] for i in vl])

m1 = random_matrix(FF, n, 1)
m0 = apply(F, m1)
while True:
    m2 = random_matrix(FF, n, 1)
    a = sha256(bytes([list(FF).index(i[0]) for i in list(m0) + list(v) + list(m2)])).digest()[0] % len(list(FF))
    print("a =", a)
    if a == 0:
        pi = (m0, m1, m2)

        res = verif_pok(v, F, pi)
        assert res == True
        print([list(FF).index(i[0]) for i in list(m0)])
        print([list(FF).index(i[0]) for i in list(m1)])
        print([list(FF).index(i[0]) for i in list(m2)])
        exit()