3414 words
17 minutes
0xGame2025-week4
2025-10-28
统计加载中...

前言#

包好的题目,不会写怎么办()

这里只写 week4 的,前几周的感觉还是挺简单的

题目#

Quaternion#

task.py

from Crypto.Util.number import *
from secret import flag
class Quaternion:
def __init__(self, value=[0, 0, 0, 0]):
if type(value) == str:
self.value = [int(i[:-1]) if i[-1] in 'ijk' else int(i) for i in value.split(' + ')]
elif type(value) == list:
self.value = value
else:
raise ValueError("Invalid type for Quaternion initialization")
def __str__(self):
s = str(self.value[0])
for k, i in enumerate(self.value[1:]):
if i >= 0:
s += ' + '
s += str(i) + 'ijk'[k]
return s
def __add__(self, x: 'Quaternion'):
return Quaternion([i + j for i, j in zip(self.value, x.value)])
def __mul__(self, x):
a = self.value[0] * x.value[0] - self.value[1] * x.value[1] - self.value[2] * x.value[2] - self.value[3] * x.value[3]
b = self.value[0] * x.value[1] + self.value[1] * x.value[0] + self.value[2] * x.value[3] - self.value[3] * x.value[2]
c = self.value[0] * x.value[2] - self.value[1] * x.value[3] + self.value[2] * x.value[0] + self.value[3] * x.value[1]
d = self.value[0] * x.value[3] + self.value[1] * x.value[2] - self.value[2] * x.value[1] + self.value[3] * x.value[0]
return Quaternion([a, b, c, d])
def __mod__(self, x: 'Quaternion'):
return Quaternion([i % x for i in self.value])
def __pow__(self, x: 'Quaternion', n=None):
tmp = Quaternion(self.value)
a = Quaternion([1, 0, 0, 0])
while x:
if x & 1:
a *= tmp
tmp *= tmp
if n:
a %= n
tmp %= n
x >>= 1
return a
p = getPrime(512)
a = 12279853477283008553357657492789763255038870106301916103237655771107240855639019740311384654049683518074767532668306533768786610265460170996795820601321909
b = 11210493117462584846695225251401309306620235323150317045189710917149538613051572049177523790232361862309737553875527265480632012163444954592595707353201593
c = 450430550110220511170706355346853127519361342092668926760254273216011478764445031538810577279916498307130264816119062480395788963919822889673835039577697
d = 3174124676656013174258111656715518261932447407587587373745001204373630194465607583875038303157271540416614763810219408013310091241179227844814818997567768
quaternion = Quaternion([a, b, c, d])
m = bytes_to_long(flag)
enc = pow(quaternion, m, p)
print(f"p = {p}")
print(f"public = \"{quaternion}\"")
print(f"enc = \"{enc}\"")

这里可以非预期(

exp.py

p = 12539865613843105911233856888270676153770546095381527984724592359575546777767977150390387506665349509251937298897654702160935929421300160106185683395163421
a = 12279853477283008553357657492789763255038870106301916103237655771107240855639019740311384654049683518074767532668306533768786610265460170996795820601321909
b = 11210493117462584846695225251401309306620235323150317045189710917149538613051572049177523790232361862309737553875527265480632012163444954592595707353201593
c = 450430550110220511170706355346853127519361342092668926760254273216011478764445031538810577279916498307130264816119062480395788963919822889673835039577697
d = 3174124676656013174258111656715518261932447407587587373745001204373630194465607583875038303157271540416614763810219408013310091241179227844814818997567768
a_prime = 2265462378761600430532145400101912094563084708128292610022357820427900202076518621271341061446425878932435592087810301613872104317791934633397153003692733
b_prime = 11782169557857342336271967041629416683561191223952219855027739818522859391187666351078682423218210372662669230179370323154138802086643543387102521811129172
c_prime = 10908162276945008648926361072155772044902584979394235365102141792914012158138256920012734972335441493996292123708617034711883958615075943172083534030307874
d_prime = 10540286960315438106114455060855856502156861387281507535140969278011407516259839611632861445654470081837664230792371993219647065751349771625197786828909473
b_inv = pow(b, -1, p)
k = (b_prime * b_inv) % p
a_prime_inv = pow(a_prime, -1, p)
m = (k * a * a_prime_inv) % p
from Crypto.Util.number import long_to_bytes
flag = long_to_bytes(m)
print(flag)

MT19937&MT19937-Revenge#

task.py

#!/usr/local/bin/python
import random
from secret import flag
seed = random.randint(0, 2**32 - 1)
RNG = random.Random(seed)
MENU = """
MT19937 Seed Challenge
======================
[G]et random number
[C]heck seed
"""
def challenge():
while True:
inputs = input(">> ")
if inputs.strip().upper() == "G":
print(RNG.getrandbits(2))
elif inputs.strip().upper() == "C":
guess = int(input("Guess the seed: "))
if guess == seed:
print(f"Correct! Here is your flag: {flag.decode()}")
else:
print("Incorrect seed. Exiting...")
break
else:
print("Invalid option. Please choose G or C.")
if __name__ == "__main__":
print("Welcome to the MT19937 Seed Challenge!")
print("A random seed has been generated. You can get random numbers or try to guess the seed.")
print(MENU)
challenge()

就是随机数预测,然后恢复 seed。MT19937-Revenge是限制了时间。只会返回 0123 四种情况,收集个 1w 组先,这里用gf2bv恢复状态,然后 z3 恢复 seed

exp.py

import sys
import time
from gf2bv import LinearSystem
from gf2bv.crypto.mt import MT19937
from pwn import context, remote
from z3 import BitVec, BitVecVal, LShR, Solver, sat
HOST, PORT = "nc1.ctfplus.cn", 24940
WORDS, WORD_BITS = 624, 32
BITS_PER_QUERY = 2
NEED = WORDS * WORD_BITS // BITS_PER_QUERY
TOTAL_QUERIES = 10000
N, M = 624, 397
MATRIX_A = 0x9908B0DF
UPPER_MASK = 0x80000000
LOWER_MASK = 0x7fffffff
def twist_expr(prev):
nxt = [None]*N
for i in range(N):
x = (prev[i] & BitVecVal(UPPER_MASK,32)) | (prev[(i+1)%N] & BitVecVal(LOWER_MASK,32))
xa = LShR(x,1) ^ ((x & BitVecVal(1,32)) * BitVecVal(MATRIX_A,32))
nxt[i] = prev[(i+M)%N] ^ xa
return nxt
def init_by_array_expr_from_seed(seed_bv):
s = [None]*N
s[0] = BitVecVal(19650218, 32)
for i in range(1, N):
s[i] = (BitVecVal(1812433253,32)*(s[i-1]^LShR(s[i-1],30))+BitVecVal(i,32))
i=1;j=0;k=N
for _ in range(k):
s[i]=(s[i]^((s[i-1]^LShR(s[i-1],30))*BitVecVal(1664525,32)))+seed_bv+BitVecVal(j,32)
i+=1;j+=1
if i>=N:
s[0]=s[N-1];i=1
if j>=1:
j=0
for _ in range(N-1):
s[i]=(s[i]^((s[i-1]^LShR(s[i-1],30))*BitVecVal(1566083941,32)))-BitVecVal(i,32)
i+=1
if i>=N:
s[0]=s[N-1];i=1
s[0]=BitVecVal(0x80000000,32)
return s
def recv_prompt(io):
try:
io.recvuntil(b">> ", timeout=2)
except Exception:
pass
def read_batch(io, batch_count=128, read_timeout=0.25):
outs=[]
buf=b""
try:
io.send(b"g\n"*batch_count)
except Exception as e:
print("[!] send error:", e)
return outs
deadline=time.time()+read_timeout
while time.time()<deadline:
try:
chunk=io.recv(4096, timeout=read_timeout)
if not chunk:
break
buf+=chunk
parts=buf.split()
new_outs=[]
for tok in parts:
if tok in (b"0",b"1",b"2",b"3"):
new_outs.append(int(tok))
if new_outs:
outs.extend(new_outs)
buf=b""
else:
if len(buf)>4096:
buf=buf[-1024:]
except Exception:
break
return outs
def collect():
context.log_level="warning"
io=remote(HOST,PORT)
recv_prompt(io)
outs=[]
batch_size=128
read_timeout=0.22
start_time=time.time()
MAX_DURATION=95
try:
while len(outs)<TOTAL_QUERIES and time.time()-start_time<MAX_DURATION:
to_send=min(batch_size,TOTAL_QUERIES-len(outs))
new=read_batch(io,batch_count=to_send,read_timeout=read_timeout)
if new:
outs.extend(new)
print(f"[+] Collected {len(outs)}/{TOTAL_QUERIES}")
if not new:
time.sleep(0.05)
except KeyboardInterrupt:
print("[!] interrupted")
return io,outs
def recover_pretwist_state(outs):
lin=LinearSystem([WORD_BITS]*WORDS)
mt=lin.gens()
rng=MT19937(mt)
zeros=[rng.getrandbits(BITS_PER_QUERY)^int(o) for o in outs]
sol=lin.solve_one(zeros)
return [int(w)&0xFFFFFFFF for w in sol]
def invert_seed_with_z3(block, allow_one_twist=True):
seed=BitVec("seed",32)
s0=init_by_array_expr_from_seed(seed)
def try_model(target):
S=Solver()
for i in range(N):
S.add(s0[i]==BitVecVal(target[i],32))
if S.check()==sat:
return S.model()[seed].as_long()&0xFFFFFFFF
return None
z=try_model(block)
if z is not None:
return z
if allow_one_twist:
tb=twist_expr(s0)
S=Solver()
for i in range(N):
S.add(tb[i]==BitVecVal(block[i],32))
if S.check()==sat:
return S.model()[seed].as_long()&0xFFFFFFFF
return None
def main():
io,outs=collect()
block=recover_pretwist_state(outs[:NEED])
seed=invert_seed_with_z3(block,allow_one_twist=True)
io.sendline(b"c")
io.recvuntil(b"Guess the seed:")
io.sendline(str(seed).encode())
try:
while True:
line=io.recvline(timeout=1)
if not line:
break
sys.stdout.write(line.decode(errors="ignore"))
except Exception:
pass
io.close()
if __name__=="__main__":
main()

AES Square#

task.py

from secret import flag
from utils import AES
import os
MENU = """
AES Machine
[E]ncrypt
[G]et Encrypted Flag
"""
def pad(msg): return msg + bytes([16 - len(msg) % 16] * (16 - len(msg) % 16))
print(MENU)
key = os.urandom(16)
cipher = AES(key, rounds=4)
enc = cipher.encrypt(pad(flag.encode()))
while True:
try:
choice = input("Choice: ").strip().upper()
if choice == 'E':
plaintext = input("Plaintext (hex): ").strip()
try:
plaintext_bytes = bytes.fromhex(plaintext)
if len(plaintext_bytes) % 16 != 0:
print("Plaintext length must be a multiple of 16 bytes.")
continue
ciphertext = cipher.encrypt(plaintext_bytes)
print("Ciphertext (hex):", ciphertext.hex())
except ValueError:
print("Invalid hex input.")
elif choice == 'G':
print("Encrypted Flag (hex):", enc.hex())
except Exception as e:
print("Error:", str(e))

Square 攻击,这里固定前 15 字节,爆破最后一个字节,爆三组交上去,一般就可以确定末轮 16 字节了,然后再回推

exp.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import context, remote
context.log_level = "error"
HOST = "nc1.ctfplus.cn"
PORT = 21405
def gen_sboxes():
S = [0x63] + [0] * 255
def r(x, s):
return ((x << s) | (x >> (8 - s))) & 0xff
p, q = 1, 1
for _ in range(255):
p = (p ^ (p * 2) ^ (27 if p >= 128 else 0)) & 0xff
q ^= (q * 2) & 0xff
q ^= (q * 4) & 0xff
q ^= (q * 16) & 0xff
q ^= (9 if q >= 128 else 0)
S[p] = q ^ r(q, 1) ^ r(q, 2) ^ r(q, 3) ^ r(q, 4) ^ 99
S_inv = [0]*256
for i in range(256):
S_inv[S[i]] = i
return S, S_inv
S, S_INV = gen_sboxes()
RCON = [0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1B,0x36]
def bxor(a, b):
return [(x ^ y) & 0xff for x, y in zip(a, b)]
def T(word, r):
w = word[1:] + word[:1]
w = [S[x] for x in w]
w[0] ^= RCON[r]
return w
def bytes_to_words16(b16):
return [list(b16[i:i+4]) for i in range(0, 16, 4)]
def words_to_bytes(words):
return bytes(sum(words, []))
def last_round_key_bytes_to_words(k4_bytes):
return [list(k4_bytes[j*4:(j+1)*4]) for j in range(4)]
def invert_key_schedule_from_last_round_key(k4_bytes, rounds=4):
W = [None]*((rounds+1)*4)
last_words = last_round_key_bytes_to_words(k4_bytes)
for j in range(4):
W[16+j] = last_words[j]
for i in range((rounds+1)*4 - 1, 3, -1):
if i % 4 == 0:
W[i-4] = bxor(W[i], T(W[i-1], i//4 - 1))
else:
W[i-4] = bxor(W[i], W[i-1])
return words_to_bytes(W[0:4])
class AES4:
Rcon = RCON
def __init__(self, key: bytes, rounds: int = 4):
self.rounds = rounds
self.key = key
self.S = S
self.S_inv = S_INV
self.W = [list(key[i:i + 4]) for i in range(0, 16, 4)]
self._key_expansion()
def _xor(self, a, b=None):
if b is None:
assert len(a) > 0
return a[0] ^ self._xor(a[1:]) if len(a) > 1 else a[0]
assert len(a) == len(b)
return [x ^ y for x, y in zip(a, b)]
def _gmul(self, a, b):
p = 0
while b:
if b & 1:
p ^= a
a = a << 1
if a >> 8:
a ^= 0b100011011
b >>= 1
return p & 0xff
def _multiply(self, a, b):
return self._xor([self._gmul(x, y) for x, y in zip(a, b)])
def _matrix_multiply(self, const, state):
return [[self._multiply(const[i], [state[k][j] for k in range(4)]) for j in range(4)] for i in range(4)]
def _permutation(self, lis, table):
return [table[i] for i in lis]
def _block_permutation(self, block, table):
return [self._permutation(row, table) for row in block]
def _left_shift(self, block, n):
return block[n:] + block[:n]
def _bytes_to_matrix(self, text: bytes) -> list:
return [[text[j * 4 + i] for j in range(4)] for i in range(4)]
def _matrix_to_bytes(self, block: list) -> bytes:
return bytes([block[j][i] for i in range(4) for j in range(4)])
def _T(self, w: list, n) -> list:
w = self._left_shift(w, 1)
w = self._permutation(w, self.S)
w[0] ^= self.Rcon[n]
return w
def _key_expansion(self):
for i in range(4, (self.rounds + 1) * 4):
if i % 4 == 0:
self.W.append(self._xor(self.W[i - 4], self._T(self.W[i - 1], i // 4 - 1)))
else:
self.W.append(self._xor(self.W[i - 4], self.W[i - 1]))
def _row_shift(self, block):
return [self._left_shift(block[i], i) for i in range(4)]
def _column_mix(self, block):
return self._matrix_multiply([self._left_shift([2, 3, 1, 1], 4 - i) for i in range(4)], block)
def _round_key_add(self, block, key):
return [self._xor(block[i], [key[j][i] for j in range(4)]) for i in range(4)]
def _encrypt(self, block):
block = self._round_key_add(block, self.W[:4])
for i in range(1, self.rounds):
block = self._block_permutation(block, self.S)
block = self._row_shift(block)
block = self._column_mix(block)
block = self._round_key_add(block, self.W[i * 4:(i + 1) * 4])
block = self._block_permutation(block, self.S)
block = self._row_shift(block)
block = self._round_key_add(block, self.W[-4:])
return block
def _inv_row_shift(self, block):
return [self._left_shift(block[i], 4 - i) for i in range(4)]
def _inv_column_mix(self, block):
return self._matrix_multiply([self._left_shift([14, 11, 13, 0x9], 4 - i) for i in range(4)], block)
def _decrypt(self, block):
block = self._round_key_add(block, self.W[-4:])
for i in range(self.rounds - 1, 0, -1):
block = self._inv_row_shift(block)
block = self._block_permutation(block, self.S_inv)
block = self._round_key_add(block, self.W[i * 4:(i + 1) * 4])
block = self._inv_column_mix(block)
block = self._inv_row_shift(block)
block = self._block_permutation(block, self.S_inv)
block = self._round_key_add(block, self.W[:4])
return block
def decrypt(self, ciphertext: bytes) -> bytes:
assert len(ciphertext) % 16 == 0
out = b''
for i in range(0, len(ciphertext), 16):
block = ciphertext[i:i+16]
block = self._bytes_to_matrix(block)
block = self._decrypt(block)
out += self._matrix_to_bytes(block)
return out
def pkcs7_unpad(data: bytes) -> bytes:
if not data: return data
pad = data[-1]
if pad == 0 or pad > 16: return data
if data.endswith(bytes([pad])*pad):
return data[:-pad]
return data
class Oracle:
def __init__(self, host, port):
self.io = remote(host, port)
self._drain_to_choice()
def _drain_to_choice(self):
self.io.recvuntil(b"Choice")
self.io.recvuntil(b":")
def get_flag_ct(self) -> bytes:
self.io.sendline(b"G")
self.io.recvuntil(b"Encrypted Flag (hex): ")
ct_hex = self.io.recvline().strip()
self._drain_to_choice()
return bytes.fromhex(ct_hex.decode())
def encrypt(self, pt16: bytes) -> bytes:
assert len(pt16) % 16 == 0
self.io.sendline(b"E")
self.io.recvuntil(b"Plaintext (hex): ")
self.io.sendline(pt16.hex().encode())
self.io.recvuntil(b"Ciphertext (hex): ")
ct_hex = self.io.recvline().strip()
self._drain_to_choice()
return bytes.fromhex(ct_hex.decode())
def recover_last_round_key(oracle, max_sets=3):
candidates = [set(range(256)) for _ in range(16)]
def collect_delta_set(vary_pos: int, base_byte: int):
cts = []
base = bytearray([base_byte]*16)
for x in range(256):
pt = base[:]
pt[vary_pos] = x
ct = oracle.encrypt(bytes(pt))
cts.append(ct)
return cts
used_sets = 0
plan = [(0,0x00), (7,0x5a), (13,0xa7), (4,0x11), (10,0x33)]
while used_sets < max_sets and any(len(c)>1 for c in candidates):
vary_pos, base_byte = plan[used_sets % len(plan)]
used_sets += 1
cts = collect_delta_set(vary_pos, base_byte)
for s in range(16):
good = set()
col = [ct[s] for ct in cts]
for k in range(256):
acc = 0
for b in col:
acc ^= S_INV[b ^ k]
if acc == 0:
good.add(k)
candidates[s] &= good
stat = [len(c) for c in candidates]
print(f"[+] After set {used_sets}: remaining candidates per byte = {stat}")
if any(len(c)!=1 for c in candidates):
if used_sets < len(plan):
vary_pos, base_byte = plan[used_sets % len(plan)]
cts = collect_delta_set(vary_pos, base_byte)
for s in range(16):
good = set()
col = [ct[s] for ct in cts]
for k in range(256):
acc = 0
for b in col:
acc ^= S_INV[b ^ k]
if acc == 0:
good.add(k)
candidates[s] &= good
k4 = bytes([next(iter(c)) if len(c)==1 else min(c) for c in candidates])
if any(len(c)!=1 for c in candidates):
print("[!] Warning: some bytes not unique; chose minimal candidate. (You can increase max_sets)")
return k4
def main():
print(f"[*] Connecting to {HOST}:{PORT} ...")
oracle = Oracle(HOST, PORT)
flag_ct = oracle.get_flag_ct()
print(f"[+] Encrypted flag ({len(flag_ct)} bytes): {flag_ct.hex()}")
k4 = recover_last_round_key(oracle, max_sets=3)
print(f"[+] Recovered last round key (round 4): {k4.hex()}")
master_key = invert_key_schedule_from_last_round_key(k4, rounds=4)
print(f"[+] Master key: {master_key.hex()}")
aes = AES4(master_key, rounds=4)
pt = aes.decrypt(flag_ct)
pt = pkcs7_unpad(pt)
s = pt.decode('latin1')
print(f"[+] Flag (raw): {pt}")
print(f"[+] Flag (str): {s}")
if __name__ == "__main__":
main()

蓉凤#

task.py

from Crypto.Util.number import *
from os import urandom,getenv
from AES import AES
import signal
import uuid
flag = getenv('FLAG',f'0xGame{{{uuid.uuid4()}}}')
if __name__ == '__main__':
print(f'[+] This is Hibiscus blooming in the glazing autumn wind.')
print(f'[+] It\'s said that some of you at 0xGame loves brute-force, which raises Hibiscus\'s interest:)')
print(f'[+] Enjoy it!')
signal.alarm(2025)
secret = urandom(16)
key = urandom(16)
aes = AES(key,4)
op = 232
def enc():
global op
if op <= 0:
print(f'[!] You\'ve ran out of encryption attempts!')
return
op -= 1
print(f'[+] You still have {op} chances to play with Hibiscus~')
print(f'[+] Hibiscus\'s encrypted secret is:{aes.encrypt(secret[:-1] + urandom(1)).hex()}')
def fetch():
try:
assert bytes.fromhex(input('[-] Enter Hibiscus\'s secret:')) == secret
print(f'[+] You did it! But how....? Definitely astonishing!!!')
print(f'[+] Can\'t be more delightful now, is it?')
print(f'[+] Anyway here\'s the flag: {flag}')
exit()
except (AssertionError, ValueError):
print(f'[!] Wrong!')
fdc = {'E':enc,'F':fetch}
while True:
fdc.get(input('[-] Your option:'),lambda: print(f'[!] Invalid input'))()

升级版,4bits,这里找到了出题人的攻击,直接编译完梭哈的。

exp.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import re
import subprocess
from pwn import remote
HOST, PORT = "nc1.ctfplus.cn", 49403
MIN_UNIQUE = 100
OUTFILE = "pdelta.txt"
SBOX = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16)
INV_SBOX = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d)
RCON = (0x01,0x02,0x04,0x08)
def collect_cts():
io = remote(HOST, PORT)
uniq = set()
io.recvuntil(b'Enjoy it!')
while len(uniq) < MIN_UNIQUE:
io.sendlineafter(b":", b"E")
io.recvuntil(b"encrypted secret is:")
hx = io.recvline().strip().decode()
uniq.add(hx)
with open(OUTFILE, "w") as f:
for h in uniq:
f.write(h + "\n")
return io, uniq
def run_square_revenge(count):
out = subprocess.check_output(["/Users/zsm/CTF/zsm/AES-Integrals/AES/build/bin/aes4-2", "-i", OUTFILE, "--count", str(count)])
m = re.findall(rb"[0-9a-fA-F]{32}", out)
keyhex = m[-1].decode()
return bytes.fromhex(keyhex)
def gmul(a, b):
p = 0
for _ in range(8):
if b & 1:
p ^= a
hi = a & 0x80
a = (a << 1) & 0xff
if hi:
a ^= 0x1b
b >>= 1
return p
def inv_mix_columns(s):
out = bytearray(16)
for i in range(0, 16, 4):
a, b, c, d = s[i:i+4]
out[i] = gmul(a, 0x0e) ^ gmul(b, 0x0b) ^ gmul(c, 0x0d) ^ gmul(d, 0x09)
out[i+1] = gmul(a, 0x09) ^ gmul(b, 0x0e) ^ gmul(c, 0x0b) ^ gmul(d, 0x0d)
out[i+2] = gmul(a, 0x0d) ^ gmul(b, 0x09) ^ gmul(c, 0x0e) ^ gmul(d, 0x0b)
out[i+3] = gmul(a, 0x0b) ^ gmul(b, 0x0d) ^ gmul(c, 0x09) ^ gmul(d, 0x0e)
return bytes(out)
def inv_shift_rows(s):
return bytes([s[0], s[13], s[10], s[7],
s[4], s[1], s[14], s[11],
s[8], s[5], s[2], s[15],
s[12], s[9], s[6], s[3]])
def inv_sub_bytes(s):
return bytes(INV_SBOX[b] for b in s)
def expand_key(key):
rk = [key]
for i in range(4):
prev = rk[-1]
t = bytes([SBOX[prev[13]] ^ RCON[i], SBOX[prev[14]], SBOX[prev[15]], SBOX[prev[12]]])
nk = bytes(prev[j] ^ t[j] for j in range(4))
for k in range(4, 16, 4):
nk += bytes(prev[j+k] ^ nk[j+k-4] for j in range(4))
rk.append(nk)
return rk
def aes4_decrypt_block(ct, key):
rks = expand_key(key)
s = bytes(c ^ k for c, k in zip(ct, rks[-1]))
s = inv_shift_rows(s)
s = inv_sub_bytes(s)
for r in range(3, 0, -1):
s = bytes(c ^ k for c, k in zip(s, rks[r]))
s = inv_mix_columns(s)
s = inv_shift_rows(s)
s = inv_sub_bytes(s)
s = bytes(c ^ k for c, k in zip(s, rks[0]))
return s
def brute_last_byte(io, prefix15):
for b in range(256):
guess = (prefix15 + bytes([b])).hex().encode()
io.sendlineafter(b":", b"F")
io.recvuntil(b"secret:")
io.sendline(guess)
resp = io.recvline(timeout=2) or b""
if b"You did it!" in resp:
print(resp.decode().strip())
print(io.recvline().decode().strip())
print(io.recvline().decode().strip())
return True
return False
if __name__ == "__main__":
io, cset = collect_cts()
key = run_square_revenge(len(cset))
print("Master key =", key.hex())
any_ct = next(iter(cset))
pt = aes4_decrypt_block(bytes.fromhex(any_ct), key)
prefix15 = pt[:-1]
print("secret[:15] =", prefix15.hex())
ok = brute_last_byte(io, prefix15)

逐望#

task.py

__import__('os').environ['TERM'] = 'xterm'
from sage.all import *
from Crypto.Util.number import *
from os import urandom,environ
from pwn import xor
import uuid
flag = environ.get('FLAG',f'0xGame{{{uuid.uuid4()}}}')
class _550C:
def __init__(self,sec: bytes = None):
self.p = 76884956397045344220809746629001649093037950200943055203735601445031516197751
self.a = 56698187605326110043627228396178346077120614539475214109386828188763884139993
self.b = 17577232497321838841075697789794520262950426058923084567046852300633325438902
self.E = EllipticCurve(Zmod(self.p),[self.a,self.b])
if sec is not None:
while True:
try:
m = sec + urandom(32-len(sec))
self.stat = self.E.lift_x(ZZ(int.from_bytes(m)))
break
except:
continue
else:
self.stat = self.E.random_point()
self.inc = self.E.random_point()
for _ in range(int.from_bytes(urandom(2))):
self.next()
def next(self):
ret = self.stat.xy()
self.stat += self.inc
return ret
def _rand550(self):
x,y = self.next()
p1 = int(x).to_bytes(32,'little').rjust(35)
p2 = int(y).to_bytes(32,'little').rjust(35)
dev1 = urandom(7).ljust(35)
dev2 = urandom(7).ljust(35)
return int.from_bytes(xor(p1,dev1)+xor(p2,dev2)) % 2**550
def getrandbits(self,bits:int):
assert bits >= 0
parts = []
for _ in range(bits // 550 + 1):
parts.append(self._rand550())
return reduce(lambda x,y: (x<<550) + y,reversed(parts),0) % 2**bits
if __name__=='__main__':
key = '0xGGGGame#' + urandom(7).hex()
print(f'[+] Initializing 550C...')
r = _550C(key.encode())
print(f'[+] Your random number: {r.getrandbits(2025)}')
if(input('[-] Your secret code:') == key):
print(f'[+] Good job! The flag is {flag}')
exit()
else:
print(f'[!] Wrong!')

其实能很快的意识到这是二元 copper?

x=X+uy=Y+vf(u,v)=(Y+v)2(X+u)3a(X+u)b=0modpx=X+u \\ y=Y+v \\ f(u,v)=(Y+v)^2-(X+u)^3-a(X+u)-b=0 \mod p

在这里面 uv 是比较小的,可以直接 copper 出来,然后再回溯求 seed,然后求 key,这里后面的步骤 ai 写的,我写前面的 copper

exp.py

solve_clean.py
import itertools
from pwn import remote
from sage.all import *
HOST = "nc1.ctfplus.cn"
PORT = 24852
p = 76884956397045344220809746629001649093037950200943055203735601445031516197751
a = 56698187605326110043627228396178346077120614539475214109386828188763884139993
b = 17577232497321838841075697789794520262950426058923084567046852300633325438902
F = GF(p)
E = EllipticCurve(F, [a, b])
N = E.order()
inv = lambda d: inverse_mod(d, N)
HEXDIGITS = set(b"0123456789abcdef")
PREFIX = b"0xGGGGame#"
def small_roots(f, bounds, m=1, d=None):
if not d:
d = f.degree()
if isinstance(f, Polynomial):
x, = polygens(f.base_ring(), f.variable_name(), 1)
f = f(x)
R = f.base_ring()
Nmod = R.cardinality()
leading = 1 / f.coefficients().pop(0)
f = f.map_coefficients(lambda x: x * leading).change_ring(ZZ)
G = Sequence([], f.parent())
for i in range(m+1):
base = Nmod**(m-i) * f**i
for shifts in itertools.product(range(d), repeat=f.nvariables()):
g = base * prod(v**e for v, e in zip(f.variables(), shifts))
G.append(g)
B, monomials = G.coefficient_matrix()
monomials = vector(monomials)
factors = [monomial(*bounds) for monomial in monomials]
for i, factor in enumerate(factors):
B.rescale_col(i, factor)
B = B.dense_matrix().LLL().change_ring(QQ)
for i, factor in enumerate(factors):
B.rescale_col(i, 1/factor)
H = Sequence([], f.parent().change_ring(QQ))
for h in filter(None, B*monomials):
H.append(h)
I = H.ideal()
if I.dimension() == 0:
roots = []
for root in I.variety(ring=ZZ):
roots.append(tuple(R(root[var]) for var in f.variables()))
return roots
return []
def split_blocks_550(Rint):
MASK = (1 << 550) - 1
return [(Rint >> (550 * i)) & MASK for i in range(4)]
def point_from_block(block):
C = int(block).to_bytes(70, "big")
p1_tail = C[7:35]
p2_tail = C[42:70]
x_bytes = [0]*32
y_bytes = [0]*32
for i in range(28):
x_bytes[4 + i] = p1_tail[i] ^ 0x20
y_bytes[4 + i] = p2_tail[i] ^ 0x20
X = sum(x_bytes[k] * (256**k) for k in range(32))
Y = sum(y_bytes[k] * (256**k) for k in range(32))
Ruv = PolynomialRing(GF(p), names=("u", "v"))
u, v = Ruv.gens()
f = (Y + v)**2 - (X + u)**3 - a*(X + u) - b
bounds = (2**32, 2**32)
sol = None
for m in [1, 2, 3]:
for d in [3, 4, 5]:
roots = small_roots(f, bounds, m=m, d=d)
if roots:
sol = roots[0]
break
if sol:
break
if not sol:
raise ValueError("Coppersmith failed")
ur, vr = map(int, sol)
x = (X + ur) % p
y = (Y + vr) % p
return E.point((F(x), F(y)))
def is_hex_ascii(bs):
return all(c in HEXDIGITS for c in bs)
def try_recover_key_from_seed_x(seed_x):
for k in range(3):
m = seed_x + k * p
if m >= (1 << 256):
continue
ms = m.to_bytes(32, "big")
if ms.startswith(PREFIX) and is_hex_ascii(ms[10:24]):
return ms[:24].decode("ascii")
return None
def recover_key(base_point, inc, offset):
B = base_point - (offset * inc)
for c in range(0, (1 << 16) + 8):
S = B - (c * inc)
key = try_recover_key_from_seed_x(int(S.xy()[0]))
if key:
return key
raise ValueError("Key not found")
def solve_once():
io = remote(HOST, PORT)
io.recvuntil(b"Your random number:")
Rint = int(io.recvline().strip().decode())
blocks = split_blocks_550(Rint)
solved = []
for idx in range(4):
try:
P = point_from_block(blocks[idx])
solved.append((idx, P))
except Exception:
pass
if len(solved) < 2:
raise RuntimeError("Not enough points")
inc = None
for (i, Pi) in solved:
for (j, Pj) in solved:
if j <= i:
continue
d = j - i
if gcd(d, N) != 1:
continue
inc = (Pj - Pi) * int(inv(d))
base_idx, base_point = i, Pi
break
if inc:
break
if inc is None:
raise RuntimeError("Failed to compute inc")
key = recover_key(base_point, inc, base_idx)
io.recvuntil(b"Your secret code:")
io.sendline(key.encode())
out = io.recvall(timeout=2).decode(errors="ignore")
print("[+] KEY =", key)
print(out)
if __name__ == "__main__":
solve_once()
0xGame2025-week4
https://www.zhuangsanmeng.xyz/posts/0xgame2025/
Author
zsm
Published at
2025-10-28
License
MIT

Some information may be outdated