💭

TBTL CTF 2024 writeup

2024/05/13に公開

Fence Building [544 solves]

I've recently got into woodworking and have build a beautiful fence just like this one.

Now I'm working on a flag, but it turns out all garbled for some reason...

T0n40g5BG03cmk0D1hr}T{dFe_3g_3buL_5_n0

fence暗号と言われているので、それを実装する。chatGPTに任せて僕はほかの問題を見ていました。

solve.py
# 正確なスプリット・レール・フェンス暗号の復号処理
def decrypt_split_rail_fence(ciphertext, num_rails):
    # 暗号文の長さ
    n = len(ciphertext)
    # レールごとにインデックスのリストを作成
    rails = [[] for _ in range(num_rails)]
    # 現在のレールと進行方向
    rail, step = 0, 1
    
    # レールパターンを決定
    for i in range(n):
        rails[rail].append(i)
        if rail == 0:
            step = 1
        elif rail == num_rails - 1:
            step = -1
        rail += step

    # レールパターンに基づいて復号
    result = [''] * n
    idx = 0
    for r in rails:
        for i in r:
            result[i] = ciphertext[idx]
            idx += 1

    return ''.join(result)

# 4レールを仮定して復号を試みる
decrypted_text_correct_4_rails_fixed = decrypt_split_rail_fence(cipher_text, 4)
decrypted_text_correct_4_rails_fixed

TBTL{G00d_F3nce5_m4k3_g00D_n31ghb0ur5}

School Essay [101 solves]

I had to write an essay for school describing my favorite classmate. I wonder if my classmates will be able to figure out who I'm describing...

chall.py
from Crypto.Util.number import *
from redacted import FLAG

ESSAY_TEMPLATE = """
My Favorite Classmate
=====================

My favorite person in this class has a beautiful smile,
great sense of humour, and lots of colorful notebooks.

However, their most distinctive feature is the fact that
you can represent their name as an integer value, square
it modulo %d,
and you'll get %d.

Additionally, When you compute the greatest integer not exceeding
the cube root of their squared name, you get %d.

By now, all of you have probably guessed who I'm talking about.
"""


def invpow3(x):
    lo, hi = 1, x
    while lo < hi:
        mid = (lo + hi) // 2 + 1
        if (mid**3) <= x:
            lo = mid
        else:
            hi = mid - 1
    return lo


N = 59557942237937483757629838075432240015613811860811898821186897952866236010569299041278104165604573

name_int = bytes_to_long(FLAG)

assert 1 < name_int < N

value_1 = (name_int**2) % N
value_2 = invpow3(name_int**2)

print(ESSAY_TEMPLATE % (N, value_1, value_2))
description.txt

My Favorite Classmate
=====================

My favorite person in this class has a beautiful smile,
great sense of humour, and lots of colorful notebooks.

However, their most distinctive feature is the fact that
you can represent their name as an integer value, square
it modulo 59557942237937483757629838075432240015613811860811898821186897952866236010569299041278104165604573,
and you'll get 34994952631013563439857468985559745199379391295940238707110695903159545061311344766055629477728657.

Additionally, When you compute the greatest integer not exceeding
the cube root of their squared name, you get 7906488397402721714607879953738472269409876715324979164781592447.

By now, all of you have probably guessed who I'm talking about.

value_2 を使わずとも、 value_1N を平方剰余で求めることができる。

solve.py
import gmpy2
from Crypto.Util.number import *

N = 59557942237937483757629838075432240015613811860811898821186897952866236010569299041278104165604573
value_1 = 34994952631013563439857468985559745199379391295940238707110695903159545061311344766055629477728657
value_2 = 7906488397402721714607879953738472269409876715324979164781592447

# https://qiita.com/taiyaki8926/items/6720a1c19e9c9afbfd12#%E5%85%A8%E4%BD%93%E3%81%AE%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89

def legendre_symbol(n, p):
    ls = pow(n, (p - 1) // 2, p)
    if ls == 1:
        return 1
    elif ls == p - 1:
        return -1
    else:
        # in case ls == 0
        raise Exception('n:{} = 0 mod p:{}'.format(n, p))

def check_sqrt(x, n, p):
    assert(pow(x, 2, p) == n % p)

def modular_sqrt(n:int, p:int) -> list:
    if type(n) != int or type(p) != int:
        raise TypeError('n and p must be integers')

    if p < 3:
        raise Exception('p must be equal to or more than 3')

    if not isPrime(p):
        raise Exception('p must be a prime number. {} is a composite number'.format(p))

    if legendre_symbol(n, p) == -1:
        raise Exception('n={} is Quadratic Nonresidue modulo p={}'.format(n, p))

    if p % 4 == 3:
        x = pow(n, (p + 1) // 4, p)
        check_sqrt(x, n, p)
        return [x, p - x]

    # Tonelli-Shanks
    q, s = p - 1, 0
    while q % 2 == 0:
        q //= 2
        s += 1
    z = 2
    while legendre_symbol(z, p) != -1:
        z += 1
    m, c, t, r = s, pow(z, q, p), pow(n, q, p), pow(n, (q + 1) // 2, p)
    while t != 1:
        pow_t = pow(t, 2, p)
        for j in range(1, m):
            if pow_t == 1:
                m_update = j
                break
            pow_t = pow(pow_t, 2, p)
        b = pow(c, int(pow(2, m - m_update - 1)), p)
        m, c, t, r = m_update, pow(b, 2, p), t * pow(b, 2, p) % p, r * b % p
    check_sqrt(r, n, p)
    return [r, p - r]

name_ints = modular_sqrt(value_1, N)

for name_int in name_ints:
    try:
        print(long_to_bytes(name_int))
    except:
        pass

TBTL{J0hn_J4c0b_J1n6leH31mer_Schm1d7_<3}

Wikipedia Signatures [84 solves]

https://en.wikipedia.org/w/index.php?title=Digital_signature&oldid=1113936031

Alice signs a message—"Hello Bob!"—by appending to the original message a version encrypted with her private key. Bob receives both the message and signature. He uses Alice's public key to verify the authenticity of the message, i.e. that the encrypted copy, decrypted using the public key, exactly matches the original message.

server.py
#!/usr/bin/python3

from redacted import FLAG

from Crypto.Util.number import bytes_to_long
from Crypto.Math.Numbers import Integer
from Crypto.PublicKey import RSA

import signal


TARGET = b'I challenge you to sign this message!'


def myprint(s):
    print(s, flush=True)


def handler(_signum, _frame):
    myprint("Time out!")
    exit(0)


def rsa(m, n, x):
    if not 0 <= m < n:
        raise ValueError("Value too large")
    return int(pow(Integer(m), x, n))


# Alice signs a message—"Hello Bob!"—by appending to the original 
# message a version encrypted with her private key. 
def wikipedia_sign(message, n, d):
    return rsa(message, n, d)


# Bob receives both the message and signature. He uses Alice's public key 
# to verify the authenticity of the message, i.e. that the encrypted copy,
# decrypted using the public key, exactly matches the original message.
def wikipedia_verify(message, signature, n, e):
    return rsa(signature, n, e) == bytes_to_long(message)


def main():
    signal.signal(signal.SIGALRM, handler)
    signal.alarm(300)

    rsa_key = RSA.generate(1024)
    public_key = (rsa_key.n, rsa_key.e)

    myprint(f"RSA public key: {public_key}")
    myprint("Options:")
    myprint(f"1 <sig> -- Submit signature for {TARGET} and win")
    myprint("2 <msg> -- Sign any other message using wikipedia-RSA")

    for _ in range(10):
        line = input("> ")
        action, data = map(int, line.split())
        if action == 1:
            if wikipedia_verify(TARGET, data, rsa_key.n, rsa_key.e):
                myprint(f"{FLAG}")
                exit(0)
            else:
                myprint(f"Nope. Keep trying!") 
        elif action == 2:
            if data % rsa_key.n == bytes_to_long(TARGET):
                myprint(f"Nope. Won't sign that!") 
            else:
                sig = wikipedia_sign(data, rsa_key.n, rsa_key.d)
            myprint(sig)
        else:
            break


if __name__ == "__main__":
    main()

任意の復号結果を受け取れてしまうので、 2^eC を復号して 2m を得ることができる。

以下のプログラムを使いながらやり取りしました。

solve.py
from Crypto.Util.number import *

N = int(input("N:"))
e = 65537

pow2 = pow(2, e, N)

TARGET = b'I challenge you to sign this message!'
c = bytes_to_long(TARGET)

send = pow2*c % N
print(f'{send=}')

recv = int(input("recv:"))

sig = recv//2 if recv%2 == 0 else (N+recv) // 2

print(f'{sig=}')

TBTL{r3p347_4f73r_m3-d16174l_516n47ur3_15_n07_3ncryp710n}

署名を適当に理解しているせいで、m, c とかの変数の解釈違いを起こしがち。そのせいでタイムロスしたもん。解けたからいいけど。

upsolve したい問題

University Paper [28 solves]

I had to write a scientific paper for one of my university courses describing my scientific role model. I wonder if my professors will be able to figure out who I'm describing...

chall.py
from Crypto.Util.number import *
from redacted import FLAG

ESSAY_TEMPLATE = """
On the Estemeed Scientifc Role Model of Mine
============================================

Within the confines of this academic setting, the individual whom
I hold in highest regard not only boasts an engaging smile but also
possesses a remarkable sense of humor, complemented by an array of
vibrant notebooks.

Yet, it is their distinct quantifiable attribute that stands out
most prominently: their name, when converted into an integer value
and squared modulo %d,
astonishingly results in %d.

Furthermore, the greatest integer that does not surpass the cube root
of the aforementioned squared name equals %d.
This computational detail adds another layer of distinction.

It is likely that by this point, many of you have discerned the identity
of this distinguished role model.
"""


def invpow3(x):
    lo, hi = 1, x
    while lo < hi:
        mid = (lo + hi) // 2 + 1
        if (mid**3) <= x:
            lo = mid
        else:
            hi = mid - 1
    return lo


N = 13113180816763040887576781992067364636289723584543479342139964290889855987378109190372819034517913477911738026253141916115785049387269347257060732629562571

name_int = bytes_to_long(FLAG)

assert 1 < name_int < N

value_1 = (name_int**2) % N
value_2 = invpow3(name_int**2)

assert (value_2**3) <= (name_int**2)
assert ((value_2 + 2) ** 3) > (name_int**2)

print(ESSAY_TEMPLATE % (N, value_1, value_2))

description.txt

On the Estemeed Scientifc Role Model of Mine
============================================

Within the confines of this academic setting, the individual whom
I hold in highest regard not only boasts an engaging smile but also
possesses a remarkable sense of humor, complemented by an array of
vibrant notebooks.

Yet, it is their distinct quantifiable attribute that stands out
most prominently: their name, when converted into an integer value
and squared modulo 13113180816763040887576781992067364636289723584543479342139964290889855987378109190372819034517913477911738026253141916115785049387269347257060732629562571,
astonishingly results in 11295696938311339473824077083449119515455766620804723271417795055153345707595152245303924808555919718654126902417279389829240793581636850443514989727075129.

Furthermore, the greatest integer that does not surpass the cube root
of the aforementioned squared name equals 25255532621039290870985214051278041571596463385115156541846401100873975663406085683775323107488.
This computational detail adds another layer of distinction.

It is likely that by this point, many of you have discerned the identity
of this distinguished role model.

どうやら N が合成数なのですが、素因数分解は難しいらしい。素因数分解ができれば 除数が 2^n の mod平方根を求める(CryptoCTF2022 starter_ecc writeup) by kurenaif で解けそう。

一方で、 value_2 の情報から、 flag の前半を求めることができた。

TBTL{7h3_0n3_4nd_0nly_--_4ndr3w_W1lL14m

問題は、後半をどうにかして求める過程なのだが、どうもわからない。

Kung Fu Cipher [17 solves]

典型問題っぽいのでサラっと知識を回収したいね。

(rev) Flagcheck [101 solves]

(misc)Your papers, please [51 solves]

Discussion