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) и затем предсказать весь дальнейший поток.