Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5Mobile wallpaper 6
654 words
3 minutes
ZeroG-CTF 2026 WriteUp
2026-05-23
统计加载中...

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, c2

output.txt

n = 78429219359517922271023478963814594552681246043944770910304760471867765174623304038843626799213010074714647155283331308571847776870166597053823412781788611608177305819593874012686298378748721435009046767613360191457980203020570462985478543330425482286818857391023923223033155751757576833456411434713984471383
e1 = 65537
e2 = 17
c1 = 71282312105868131740394478794008286284074152062907735987516077413351604126882776234623911447307962528308126218712123568701353026231889282844009867916343556840839139885445525543186695511199429927944296268193188530317628821728534582820389657490317666947095834711636160892093284048993666399747630728635978820198
c2 = 70751964066395185933408819650408191047287659276501425712138199434404000627978244880544478152411510337684996008892030606559772725285766060014956720285732207231186134371296910276169402781919701351782279058927891124229021162906608266092058475936622126108377526917909078829421351716554783863514592590874754685769

README.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 gcdext
from Crypto.Util.number import long_to_bytes
n = 78429219359517922271023478963814594552681246043944770910304760471867765174623304038843626799213010074714647155283331308571847776870166597053823412781788611608177305819593874012686298378748721435009046767613360191457980203020570462985478543330425482286818857391023923223033155751757576833456411434713984471383
e1 = 65537
e2 = 17
c1 = 71282312105868131740394478794008286284074152062907735987516077413351604126882776234623911447307962528308126218712123568701353026231889282844009867916343556840839139885445525543186695511199429927944296268193188530317628821728534582820389657490317666947095834711636160892093284048993666399747630728635978820198
c2 = 70751964066395185933408819650408191047287659276501425712138199434404000627978244880544478152411510337684996008892030606559772725285766060014956720285732207231186134371296910276169402781919701351782279058927891124229021162906608266092058475936622126108377526917909078829421351716554783863514592590874754685769
g, s1, s2 = gcdext(e1, e2)
m = (pow(c1, s1, n) * pow(c2, s2, n)) % n
print(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 = 170141183460469231731687303715884105727
leak_states = [
48077378362307815584689819960136019875,
100310108693164117002347749113390493183,
145646689101109657050476193569066602802,
63949818470656288394594660187785964270,
46314465195318558087862397882705709486,
103138436636073932218183299598776830813,
]
ciphertext = 39fe07de62fdc9bf74bbbcbd7e202386ca9e40451b46c74968e30fff138a95

README.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_statesm求出ac,即可恢复后面 32 个异或密钥的值,然后解密即可。

exp.py

m = 170141183460469231731687303715884105727
leak_states = [
48077378362307815584689819960136019875,
100310108693164117002347749113390493183,
145646689101109657050476193569066602802,
63949818470656288394594660187785964270,
46314465195318558087862397882705709486,
103138436636073932218183299598776830813,
]
ciphertext = bytes.fromhex("39fe07de62fdc9bf74bbbcbd7e202386ca9e40451b46c74968e30fff138a95")
# 计算差值模m
d0 = (leak_states[1] - leak_states[0]) % m
d1 = (leak_states[2] - leak_states[1]) % m
# 求逆
inv_d0 = pow(d0, -1, m)
a = (d1 * inv_d0) % m
c = (leak_states[1] - a * leak_states[0]) % m
print(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 result

output.txt

e = 3
n1 = 9203118261705868019110006623273896134322296004495934622126321588206198211590594608536574205500841860912183113474492528101942483463604127057100041845594123
c1 = 225326225723570437926892098700724301640108952320044616725184090895511961737080288471190011942447422341235122945729017303171992927231675218640713872178033
n2 = 8218974785294030613346971087108222043759818458429043768635262660088269400867661193359046399568686339887944628791712180696779799918022646158973494803220299
c2 = 3407676048044393024576659577470571794093695115844258472643168272782162860244002027327745232045383478691907846926814490953793141526176684717238078901972654
n3 = 8640442409248695297781745462901828098989267118787634310572918885729221856234292677073935037333836295724444289085611427540896246989248186559475612627680863
c3 = 6492260343134932927953198433174002823828534869771319070490239692685600132982822403083735209163800494671140850876058194194328293660168048521787716473266503

README.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_bytes
from gmpy2 import iroot
from sympy.ntheory.residue_ntheory import crt
e = 3
n1 = 9203118261705868019110006623273896134322296004495934622126321588206198211590594608536574205500841860912183113474492528101942483463604127057100041845594123
c1 = 225326225723570437926892098700724301640108952320044616725184090895511961737080288471190011942447422341235122945729017303171992927231675218640713872178033
n2 = 8218974785294030613346971087108222043759818458429043768635262660088269400867661193359046399568686339887944628791712180696779799918022646158973494803220299
c2 = 3407676048044393024576659577470571794093695115844258472643168272782162860244002027327745232045383478691907846926814490953793141526176684717238078901972654
n3 = 8640442409248695297781745462901828098989267118787634310572918885729221856234292677073935037333836295724444289085611427540896246989248186559475612627680863
c3 = 6492260343134932927953198433174002823828534869771319070490239692685600132982822403083735209163800494671140850876058194194328293660168048521787716473266503
m3 = 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/
Author
Q1uJu
Published at
2026-05-23
License
CC BY-NC-SA 4.0

Some information may be outdated