July 4

GRSU Summer Practice Crypto райтапы


Не Придумал Название

RSA это асимметричный шифр, на который есть множество атак. Например Boneh-Durfee attack.
Формат флага: **grodno{}**

Не вижу смысла объяснять теорию про рса и описывать вектор атаки и тд. и тп.
Все равно вам это не интересно и не нужно. Никогда не поверю, что ее решали руками.
Вся криптография решалась через ИИшку:

Кидаем файлы задачи, кидаем название и описание и ждем флаг:
grodno{l4tt1c3s_wh1sp3r_wh3n_d_1s_t00_sm0l}

Пример рабочего эксплойта:

from math import isqrt
from pathlib import Path
import re
DEFAULT_PATH = Path("/tmp/chal1/ciphertext.txt")
def continued_fraction(numerator: int, denominator: int):
    while denominator:
        q = numerator // denominator
        yield q
        numerator, denominator = denominator, numerator - q * denominator
def convergents(sequence):
    n0, d0 = 0, 1
    n1, d1 = 1, 0
    for a in sequence:
        n2 = a * n1 + n0
        d2 = a * d1 + d0
        yield n2, d2
        n0, d0, n1, d1 = n1, d1, n2, d2
def parse_values(text: str) -> tuple[int, int, int]:
    values = {}
    for name in ("n", "e", "c"):
        match = re.search(rf"{name}\s*=\s*(\d+)", text)
        if not match:
            raise ValueError(f"missing {name}")
        values[name] = int(match.group(1))
    return values["n"], values["e"], values["c"]
def int_to_bytes(value: int) -> bytes:
    hex_value = f"{value:x}"
    if len(hex_value) % 2:
        hex_value = "0" + hex_value
    return bytes.fromhex(hex_value)
def wiener_attack(n: int, e: int) -> int | None:
    for k, d in convergents(continued_fraction(e, n)):
        if k == 0 or (e * d - 1) % k:
            continue
        phi = (e * d - 1) // k
        s = n - phi + 1
        discriminant = s * s - 4 * n
        if discriminant < 0:
            continue
        t = isqrt(discriminant)
        if t * t != discriminant or (s + t) % 2:
            continue
        p = (s + t) // 2
        q = (s - t) // 2
        if p * q == n:
            return d
    return None
def main():
    text = DEFAULT_PATH.read_text(encoding="utf-8")
    n, e, c = parse_values(text)
    d = wiener_attack(n, e)
    if d is None:
        raise SystemExit("Wiener attack failed")
    message = pow(c, d, n)
    print(int_to_bytes(message).decode())
if __name__ == "__main__":
    main()


Флаг: grodno{l4tt1c3s_wh1sp3r_wh3n_d_1s_t00_sm0l}




Я Твой ROT...

Мой китайский друг отправил мне это, но я не понял что он хотел сказать.
籰类籸籭籷籸粄籟籨籛簹籝籁簹簹簹籨籶籷簼籨籗簹籰籲粆


Можно использовать простой гпт, а можно воспользоваться специальным сайтом для шифров - CyberChef

Использовался шифр ROT8000, который сдвигает каждый символ на 8000 по юникод таблице.

Флаг: grodno{V_R0T8000_mn3_N0gi}

База 64

Даже описание придумывать не хочется, все есть в названии.
Формат флага: grodno{}


Как и сказано в описании, все есть в названии.
Шифр называется Base64
Снова воспользуемся кибер шефом:

Проще конечно же было использовать ГПТ, но и так пойдет.

Флаг: grodno{barabarabarabereberebere}




Бу! Испугался? Не бойся

Оказывается криптографию можно засунуть даже в ехе.
Сможете ли вы достать флаг из этого точно не вирусного файла?)
Формат флага: **grodno{}**


Запустив файл у себя на устройстве, появляется безобидный скринлокер, который закрывается через 40 секунд и не несет никакой полезной нагрузки.
Значит нам нужен не сам файл, а то что лежит внутри него.

Ультимативный способ решения почти любой крипто задачи:
ЧатГПТ, помоги, у меня есть такая-то задача, такое-то описание и такие-то файлы, достань флаг -> уходим пить чай -> приходим и получаем флаг.

Флаг: grodno{I_f33l_y0ur_f3@r_bug@g@5h3nka}


Qkd Entropy

Лаборатория прислала дамп своего “квантово-устойчивого” канала распределения ключа. Они уверяют, что это честный BB84, а синхронизационный маяк никак не влияет на безопасность.
Формат флага: grodno{}


Интересная задача, которая реализует псевдо-квантовый алгоритм шифрования.
Метод действия точно такой-же как и раньше - ЧатГПТ.

Флаг: grodno{7h3r3_1s_n0_sp00n_0nly_b4d_b34c0n5}

Пример рабочего солвера: 

#!/usr/bin/env python3
from __future__ import annotations
import hashlib
import json
from pathlib import Path
from z3 import BitVec, BitVecVal, LShR, Solver
MASK = (1 << 64) - 1
def xs128p_step_z3(s0, s1):
    x = s0
    y = s1
    new_s0 = y
    x = x ^ ((x << 23) & BitVecVal(MASK, 64))
    x = x ^ LShR(x, 17)
    x = x ^ y
    x = x ^ LShR(y, 26)
    new_s1 = x
    out = (new_s0 + new_s1) & BitVecVal(MASK, 64)
    return new_s0, new_s1, out
class XorShift128Plus:
    def __init__(self, s0: int, s1: int) -> None:
        self.s0 = s0 & MASK
        self.s1 = s1 & MASK
    def next_u64(self) -> int:
        x = self.s0
        y = self.s1
        self.s0 = y
        x ^= (x << 23) & MASK
        x ^= x >> 17
        x ^= y
        x ^= y >> 26
        self.s1 = x & MASK
        return (self.s0 + self.s1) & MASK
def take_bits(rng: XorShift128Plus, count: int) -> list[int]:
    bits: list[int] = []
    while len(bits) < count:
        word = rng.next_u64()
        for i in range(64):
            bits.append((word >> i) & 1)
            if len(bits) == count:
                break
    return bits
def bits_to_bytes(bits: list[int]) -> bytes:
    out = bytearray()
    for i in range(0, len(bits), 8):
        value = 0
        for shift, bit in enumerate(bits[i : i + 8]):
            value |= (bit & 1) << shift
        out.append(value)
    return bytes(out)
def recover_seed(beacon_words: list[int]) -> tuple[int, int]:
    s0 = BitVec("s0", 64)
    s1 = BitVec("s1", 64)
    solver = Solver()
    cur0, cur1 = s0, s1
    for observed in beacon_words:
        cur0, cur1, out = xs128p_step_z3(cur0, cur1)
        solver.add(out == BitVecVal(observed, 64))
    assert solver.check().r == 1
    model = solver.model()
    return model[s0].as_long(), model[s1].as_long()
def main() -> None:
    root = Path(__file__).resolve().parents[1]
    capture = json.loads((root / "public" / "capture.json").read_text())
    beacon_words = [int(word, 16) for word in capture["beacon_words"]]
    seed_s0, seed_s1 = recover_seed(beacon_words)
    rng = XorShift128Plus(seed_s0, seed_s1)
    for _ in beacon_words:
        rng.next_u64()
    pulse_count = capture["pulse_count"]
    alice_bits = take_bits(rng, pulse_count)
    alice_bases = take_bits(rng, pulse_count)
    bob_bases = take_bits(rng, pulse_count)
    sift_positions = [i for i, (a, b) in enumerate(zip(alice_bases, bob_bases)) if a == b]
    raw_key = [alice_bits[i] for i in sift_positions]
    sample_positions = capture["sample_positions"]
    sample_bits = [int(bit) for bit in capture["sample_bits"]]
    for pos, bit in zip(sample_positions, sample_bits):
        assert raw_key[pos] == bit
    sample_set = set(sample_positions)
    final_key_bits = [bit for i, bit in enumerate(raw_key) if i not in sample_set]
    nonce = bytes.fromhex(capture["nonce"])
    ciphertext = bytes.fromhex(capture["ciphertext"])
    keystream = hashlib.shake_256(nonce + bits_to_bytes(final_key_bits)).digest(len(ciphertext))
    plaintext = bytes(a ^ b for a, b in zip(ciphertext, keystream))
    print(plaintext.decode())
if __name__ == "__main__":
    main()

Задача мимикрирует под BB84, но ломается не на “квантовой” части, а на плохой инженерии.


Уязвимость: один и тот же XorShift128Plus используется и для публичного синхромаяка.


Первые 8 слов маяка публикуются в capture.json как beacon_words. Этого хватает, чтобы восстановить внутреннее состояние PRNG через SMT
(z3) и затем предсказать весь дальнейший поток.

Флаг: grodno{7h3r3_1s_n0_sp00n_0nly_b4d_b34c0n5}