654 words
3 minutes
ZeroG-CTF 2026 WriteUp
Crypto
Cry_01.Twin Orbit / 双轨加密
encrypt.py:
#!/usr/bin/env python3# -*- coding: utf-8 -*-
from Crypto.Util.number import bytes_to_long
def encrypt_message(flag: bytes, n: int): m = bytes_to_long(flag)
e1 = 65537 e2 = 17
c1 = pow(m, e1, n) c2 = pow(m, e2, n)
return e1, e2, c1, c2output.txt:
n = 78429219359517922271023478963814594552681246043944770910304760471867765174623304038843626799213010074714647155283331308571847776870166597053823412781788611608177305819593874012686298378748721435009046767613360191457980203020570462985478543330425482286818857391023923223033155751757576833456411434713984471383e1 = 65537e2 = 17c1 = 71282312105868131740394478794008286284074152062907735987516077413351604126882776234623911447307962528308126218712123568701353026231889282844009867916343556840839139885445525543186695511199429927944296268193188530317628821728534582820389657490317666947095834711636160892093284048993666399747630728635978820198c2 = 70751964066395185933408819650408191047287659276501425712138199434404000627978244880544478152411510337684996008892030606559772725285766060014956720285732207231186134371296910276169402781919701351782279058927891124229021162906608266092058475936622126108377526917909078829421351716554783863514592590874754685769README.txt:
ZeroG-CTF Crypto 1 - Twin Orbit
Two orbit modules encrypted the same message with RSA.
Known:- Same n- Different e- Same plaintext
Recover the plaintext.
Flag format:flag{...}也是给了个很多提示,总之是个共模攻击的题,gcdext就行。
exp.py:
from gmpy2 import gcdextfrom Crypto.Util.number import long_to_bytes
n = 78429219359517922271023478963814594552681246043944770910304760471867765174623304038843626799213010074714647155283331308571847776870166597053823412781788611608177305819593874012686298378748721435009046767613360191457980203020570462985478543330425482286818857391023923223033155751757576833456411434713984471383e1 = 65537e2 = 17c1 = 71282312105868131740394478794008286284074152062907735987516077413351604126882776234623911447307962528308126218712123568701353026231889282844009867916343556840839139885445525543186695511199429927944296268193188530317628821728534582820389657490317666947095834711636160892093284048993666399747630728635978820198c2 = 70751964066395185933408819650408191047287659276501425712138199434404000627978244880544478152411510337684996008892030606559772725285766060014956720285732207231186134371296910276169402781919701351782279058927891124229021162906608266092058475936622126108377526917909078829421351716554783863514592590874754685769g, s1, s2 = gcdext(e1, e2)m = (pow(c1, s1, n) * pow(c2, s2, n)) % nprint(long_to_bytes(m).decode())# flag{ZeroG_common_modulus_attack}Cry_02.Lunar LCG / 月面伪随机
encrypt.py:
#!/usr/bin/env python3# -*- coding: utf-8 -*-
class LunarLCG: def __init__(self, m, a, c, state): self.m = m self.a = a self.c = c self.state = state
def next_state(self): self.state = (self.a * self.state + self.c) % self.m return self.state
def next_byte(self): """ The relay station uses the lowest 8 bits of each new state as one byte of keystream. """ return self.next_state() & 0xff
def xor_encrypt(data: bytes, prng: LunarLCG) -> bytes: out = bytearray()
for b in data: k = prng.next_byte() out.append(b ^ k)
return bytes(out)output.txt:
m = 170141183460469231731687303715884105727leak_states = [ 48077378362307815584689819960136019875, 100310108693164117002347749113390493183, 145646689101109657050476193569066602802, 63949818470656288394594660187785964270, 46314465195318558087862397882705709486, 103138436636073932218183299598776830813,]ciphertext = 39fe07de62fdc9bf74bbbcbd7e202386ca9e40451b46c74968e30fff138a95README.txt:
ZeroG-CTF Crypto 2 - Lunar LCG
The lunar relay station uses a Linear Congruential Generator to build a byte-wise XOR keystream.
Some consecutive PRNG states were leaked before encryption.
Recover the flag.
Flag format:flag{...}也是给了很多信息的一题,通过leak_states和m求出a和c,即可恢复后面 32 个异或密钥的值,然后解密即可。
exp.py:
m = 170141183460469231731687303715884105727leak_states = [ 48077378362307815584689819960136019875, 100310108693164117002347749113390493183, 145646689101109657050476193569066602802, 63949818470656288394594660187785964270, 46314465195318558087862397882705709486, 103138436636073932218183299598776830813,]ciphertext = bytes.fromhex("39fe07de62fdc9bf74bbbcbd7e202386ca9e40451b46c74968e30fff138a95")
# 计算差值模md0 = (leak_states[1] - leak_states[0]) % md1 = (leak_states[2] - leak_states[1]) % m# 求逆inv_d0 = pow(d0, -1, m)a = (d1 * inv_d0) % mc = (leak_states[1] - a * leak_states[0]) % mprint(f"a = {a}")print(f"c = {c}")# 验证for i in range(1, 6): assert (a * leak_states[i-1] + c) % m == leak_states[i]# 生成后续状态state = leak_states[-1]keystream = []for i in range(32): state = (a * state + c) % m keystream.append(state & 0xff)plain = bytes([c ^ k for c, k in zip(ciphertext, keystream)])print(plain.decode())# a = 47706504925832043350690201375217556277# c = 106332766362414553063251587032479728762# flag{ZeroG_lcg_stream_recovery}Cry_03.Phobos Padding / 火卫一填充
encrypt.py:
#!/usr/bin/env python3# -*- coding: utf-8 -*-
from Crypto.Util.number import bytes_to_long
def encrypt(flag: bytes, public_keys): """ public_keys: [ (n1, e), (n2, e), (n3, e), ]
Warning: This demo intentionally uses raw RSA without padding. """ m = bytes_to_long(flag)
result = []
for n, e in public_keys: c = pow(m, e, n) result.append((n, e, c))
return resultoutput.txt:
e = 3
n1 = 9203118261705868019110006623273896134322296004495934622126321588206198211590594608536574205500841860912183113474492528101942483463604127057100041845594123c1 = 225326225723570437926892098700724301640108952320044616725184090895511961737080288471190011942447422341235122945729017303171992927231675218640713872178033
n2 = 8218974785294030613346971087108222043759818458429043768635262660088269400867661193359046399568686339887944628791712180696779799918022646158973494803220299c2 = 3407676048044393024576659577470571794093695115844258472643168272782162860244002027327745232045383478691907846926814490953793141526176684717238078901972654
n3 = 8640442409248695297781745462901828098989267118787634310572918885729221856234292677073935037333836295724444289085611427540896246989248186559475612627680863c3 = 6492260343134932927953198433174002823828534869771319070490239692685600132982822403083735209163800494671140850876058194194328293660168048521787716473266503README.txt:
ZeroG-CTF Crypto 3 - Phobos Padding
The same core command was broadcast to three RSA receivers.
Known:- e = 3- different n- same plaintext- no padding
Recover the flag.
Flag format:flag{...}crt求出m ** 3后求三次根即可。
exp.py:
from Crypto.Util.number import long_to_bytesfrom gmpy2 import irootfrom sympy.ntheory.residue_ntheory import crt
e = 3n1 = 9203118261705868019110006623273896134322296004495934622126321588206198211590594608536574205500841860912183113474492528101942483463604127057100041845594123c1 = 225326225723570437926892098700724301640108952320044616725184090895511961737080288471190011942447422341235122945729017303171992927231675218640713872178033n2 = 8218974785294030613346971087108222043759818458429043768635262660088269400867661193359046399568686339887944628791712180696779799918022646158973494803220299c2 = 3407676048044393024576659577470571794093695115844258472643168272782162860244002027327745232045383478691907846926814490953793141526176684717238078901972654n3 = 8640442409248695297781745462901828098989267118787634310572918885729221856234292677073935037333836295724444289085611427540896246989248186559475612627680863c3 = 6492260343134932927953198433174002823828534869771319070490239692685600132982822403083735209163800494671140850876058194194328293660168048521787716473266503m3 = crt([n1, n2, n3], [c1, c2, c3])[0]m = iroot(m3, e)[0]print(long_to_bytes(m).decode())# flag{ZeroG_hastad_broadcast_attack} ZeroG-CTF 2026 WriteUp
https://q1uju.cc/posts/zerog-ctf-2026-writeup/ Some information may be outdated









