lit_xor_two_story

题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
~~~

```python
#!/usr/bin/env python3
"""
LitCTF2026 — One-time pad reused for two messages (40 bytes each).

Players receive output.txt and README; they do not receive secret.py.
"""
from __future__ import annotations

import argparse
import os
from pathlib import Path

try:
from secret import M1_FLAG
except ImportError:
raise SystemExit(
"secret.py (organizer) is required to generate ciphertext; "
"players work from output.txt only."
)

# Public second message — duplicated in README for contestants.
M2_KNOWN = b"litctf2026_xor_keystream_reuse_40bytes!!"

assert len(M1_FLAG) == len(M2_KNOWN) == 40


def xor_bytes(a: bytes, b: bytes) -> bytes:
return bytes(x ^ y for x, y in zip(a, b))


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"--write",
type=Path,
help="Write hex lines to file.",
)
args = parser.parse_args()

n = len(M1_FLAG)
k = os.urandom(n)
c1 = xor_bytes(M1_FLAG, k)
c2 = xor_bytes(M2_KNOWN, k)

lines = [
f"c1 = {c1.hex()}",
f"c2 = {c2.hex()}",
f"len = {n}",
]
text = "\n".join(lines) + "\n"
print(text, end="")
if args.write:
args.write.write_text(text, encoding="utf-8")


if __name__ == "__main__":
main()

# c1 = 5f70a847ce12759e156e3cad1aa9530a119386a02ffc1c31bf14ab7a0a82ccc108f8476f75c98a28
# c2 = 5f70a847ce123cc153283ca710ae7f042b8490a238eb2228970fad6a2694f2985dc5557e69e5f474
# len = 40
```



## 解题代码



```python
c1_hex = "5f70a847ce12759e156e3cad1aa9530a119386a02ffc1c31bf14ab7a0a82ccc108f8476f75c98a28"
c2_hex = "5f70a847ce123cc153283ca710ae7f042b8490a238eb2228970fad6a2694f2985dc5557e69e5f474"


c1 = bytes.fromhex(c1_hex)
c2 = bytes.fromhex(c2_hex)
M2 = b"litctf2026_xor_keystream_reuse_40bytes!!"
M1 = bytes(a ^ b ^ c for a, b, c in zip(c1, c2, M2))
print(M1)

'''
c1=M1⊕k
c2=M2⊕k
将两式 XOR:
c1⊕c2=(M1⊕k)⊕(M2⊕k)=M1⊕M2
因此:M1=(c1⊕c2)⊕M2
'''

```

```python


```









# lit_elgamal_handshake

## 题目

~~~python
#!/usr/bin/env python3
"""
LitCTF2026 — ElGamal handshake (story)
Someone left debug logging on; the private exponent x was printed alongside ciphertext.
"""
from __future__ import annotations

import argparse
from pathlib import Path
from random import randrange

from Crypto.Util.number import bytes_to_long, getPrime, getRandomRange

try:
from secret import FLAG
except ImportError as e:
raise SystemExit("secret.py (FLAG) is required to encrypt.") from e


def generate_elgamal_keypair(bits: int = 512) -> tuple[int, int, int, int]:
p = getPrime(bits)
for _ in range(1000):
g = getRandomRange(2, min(6, p - 1))
if pow(g, (p - 1) // 2, p) != 1:
break
else:
raise RuntimeError("could not find suitable g")
x = randrange(2, p - 1)
y = pow(g, x, p)
return p, g, y, x


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"--write",
type=Path,
help="Write captured output to this file (for organizers).",
)
args = parser.parse_args()

p, g, y, x = generate_elgamal_keypair(bits=512)
k = randrange(1, p - 2)
m = bytes_to_long(FLAG)
if m >= p:
raise ValueError("flag too large for chosen p — shorten FLAG")

c1 = pow(g, k, p)
c2 = (m * pow(y, k, p)) % p

lines = [
"=== Public key (p, g, y) ===",
f"p = {p}",
f"g = {g}",
f"y = {y}",
"",
"=== Ciphertext (c1, c2) ===",
f"c1 = {c1}",
f"c2 = {c2}",
"",
"# [DEBUG] prod accidentally logged the long-term secret:",
f"x = {x}",
]
text = "\n".join(lines) + "\n"
print(text, end="")
if args.write:
args.write.write_text(text, encoding="utf-8")


if __name__ == "__main__":
main()

# === Public key (p, g, y) ===
# p = 9000784855376359808051354825193962042770028561343848432778443672755982397391267124312572697249531643069409873722736348916207732622884411596948807031140651
# g = 3
# y = 269130883529708333054320571854006406481346665463416017026083074488011546059928157925990665431751017523964760326934454181952822744463714981243407307134357

# === Ciphertext (c1, c2) ===
# c1 = 5245857426274383693193378669425243235151460522527004924092730024427525619244222247576829782077334810173274945751493387545849499010408499951268967774043627
# c2 = 6059939492718262451327758167005534191200936922719178843825888167191062504030471358635203794720371216217447404436172970111033824674731063386612549785069654

# # [DEBUG] prod accidentally logged the long-term secret:
# x = 6333662932190226841086284837534236574773242538336571410337629717617476693446496678870023479078822412461192231264928632918867512055053600497937288513718

解题代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
p = 9000784855376359808051354825193962042770028561343848432778443672755982397391267124312572697249531643069409873722736348916207732622884411596948807031140651
g = 3
y = 269130883529708333054320571854006406481346665463416017026083074488011546059928157925990665431751017523964760326934454181952822744463714981243407307134357
x = 633366293219022684108628483753423657477324253833657141033762971761747669344649667887002347907882241246119223126492863291886751205505360049793728851371884
c1 = 5245857426274383693193378669425243235151460522527004924092730024427525619244222247576829782077334810173274945751493387545849499010408499951268967774043627
c2 = 6059939492718262451327758167005534191200936922719178843825888167191062504030471358635203794720371216217447404436172970111033824674731063386612549785069654


# 解密得到 m
s = pow(c1, x, p) # s = y^k mod p
m = (c2 * pow(s, -1, p)) % p

# 将整数转回 flag
flag_bytes = m.to_bytes((m.bit_length() + 7) // 8, 'big')
print(flag_bytes)

lit_rsa_neighbor

题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
~~~

```python
#!/usr/bin/env python3
"""
LitCTF2026 — RSA where q is 'far' along the prime line but still close enough to p for Fermat.
"""
from __future__ import annotations

import argparse
from pathlib import Path

import gmpy2
from Crypto.Util.number import bytes_to_long, getPrime

try:
from secret import FLAG, NEXT_PRIME_STEPS
except ImportError as e:
raise SystemExit(
"secret.py is required to generate output (FLAG, NEXT_PRIME_STEPS)."
) from e

E = 65537


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"--write",
type=Path,
help="Write n, c to this file.",
)
args = parser.parse_args()

p = getPrime(512)
q = p
for _ in range(NEXT_PRIME_STEPS):
q = int(gmpy2.next_prime(q))

n = p * q
m = bytes_to_long(FLAG)
if m >= n:
raise ValueError("flag too large for n")

c = pow(m, E, n)

lines_players = [f"{n = }", f"{c = }", f"e = {E}"]
text = "\n".join(lines_players) + "\n"
print(text, end="")
if args.write:
args.write.write_text(text, encoding="utf-8")


if __name__ == "__main__":
main()

# n = 139637440016232025690294457609899605991056011052010466558411851317943636600860419882966079629826706361935550982744312593243181819999590825159611186779613601241742349986440676188542381451066058816661317621009248513651083772907520139375108426466691332559612971244160246310746215067136490772061317571744230078911
# c = 81172369642931859390486697024961350889751244109623802937988620847486863147682579984823958801948701482096140632580173113959531836503723522945335985723867818778699337807630592078265626995722998378992215523352858561923474395550395284015986525513984910021995657780411466237306614109262460764382539311725297619429
# e = 65537
```



## 解题代码



~~~python
import math
import gmpy2

n = 139637440016232025690294457609899605991056011052010466558411851317943636600860419882966079629826706361935550982744312593243181819999590825159611186779613601241742349986440676188542381451066058816661317621009248513651083772907520139375108426466691332559612971244160246310746215067136490772061317571744230078911
c = 81172369642931859390486697024961350889751244109623802937988620847486863147682579984823958801948701482096140632580173113959531836503723522945335985723867818778699337807630592078265626995722998378992215523352858561923474395550395284015986525513984910021995657780411466237306614109262460764382539311725297619429
e = 65537

def fermat_factor(n):
a = gmpy2.isqrt(n) + 1
while True:
b2 = a*a - n
if b2 < 0:
a += 1
continue
b = gmpy2.isqrt(b2)
if b*b == b2:
p = a - b
q = a + b
return int(p), int(q)
a += 1

p, q = fermat_factor(n)
print(f"Found factors: p = {p}\nq = {q}")

phi = (p-1)*(q-1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)

from Crypto.Util.number import long_to_bytes
print("Flag:", long_to_bytes(m))

'''
费马分解专门对付「两个质因数非常接近」的 n.
n=p×q
令 (p = a - b,q = a + b),那么:
n = (a-b)(a+b) = a^2 - b^2
即:a^2 - n = b^2,只要找到一个整数 a,使得 a^2 - n是完全平方数,就能算出p,q。
'''

lit_tiny_key_aes

题目

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/usr/bin/env python3
"""
LitCTF2026 — AES-128-ECB with a mostly fixed key (weak operational policy).
"""
from __future__ import annotations

import argparse
from pathlib import Path

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

try:
from secret import FLAG, UNKNOWN_KEY_SUFFIX
except ImportError as e:
raise SystemExit(
"secret.py is required to generate ciphertext (contains FLAG and key suffix)."
) from e

KEY_PREFIX = b"LitCTF2026!!!" # 13 bytes; 3 bytes brute-forced
assert len(KEY_PREFIX) + len(UNKNOWN_KEY_SUFFIX) == 16


def encrypt_aes_ecb_pkcs7(plaintext: bytes, key: bytes) -> bytes:
cipher = AES.new(key, AES.MODE_ECB)
return cipher.encrypt(pad(plaintext, AES.block_size))


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"--write",
type=Path,
help="Write ciphertext hex to this file.",
)
args = parser.parse_args()

key = KEY_PREFIX + UNKNOWN_KEY_SUFFIX
c = encrypt_aes_ecb_pkcs7(FLAG, key)
line = f"c = {c!r}\n"
print(line, end="")
if args.write:
args.write.write_text(line, encoding="utf-8")


if __name__ == "__main__":
main()

# c = b"\x0c\xdb'`\xc91\xf7\x05\x91+\x0fM\xed\xbc\x9b\xf1\xd8D\xcd\xfd\x0c\xb9\xb6\xb2J<\x86\x19\x06K\xb3\xa2\xa4\x18\x87<v\xac\x1bbu#\xaa\xb5I\x7f\xd8\xd3"

解题代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import itertools

ciphertext = b"\x0c\xdb'`\xc91\xf7\x05\x91+\x0fM\xed\xbc\x9b\xf1\xd8D\xcd\xfd\x0c\xb9\xb6\xb2J<\x86\x19\x06K\xb3\xa2\xa4\x18\x87<v\xac\x1bbu#\xaa\xb5I\x7f\xd8\xd3"
KEY_PREFIX = b"LitCTF2026!!!"

def try_key(suffix):
key = KEY_PREFIX + suffix
cipher = AES.new(key, AES.MODE_ECB)
decrypted = cipher.decrypt(ciphertext)
try:
plaintext = unpad(decrypted, AES.block_size)
if plaintext.startswith(b'litctf{'):
return plaintext
except:
pass
return None

# 使用 itertools.product 生成所有 3 字节组合
for suffix_bytes in itertools.product(range(256), repeat=3):#Python 里生成「笛卡尔积」的函数,专门用来穷举所有组合
suffix = bytes(suffix_bytes)
result = try_key(suffix)
if result:
print(f"Found! Key suffix: {suffix.hex()}")
print(f"Flag: {result.decode()}")
break

总体来说比较简单,基本都可以用AI直接解。