3414 words
17 minutes
0xGame2025-week4
前言
包好的题目,不会写怎么办()
这里只写 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 = 12279853477283008553357657492789763255038870106301916103237655771107240855639019740311384654049683518074767532668306533768786610265460170996795820601321909b = 11210493117462584846695225251401309306620235323150317045189710917149538613051572049177523790232361862309737553875527265480632012163444954592595707353201593c = 450430550110220511170706355346853127519361342092668926760254273216011478764445031538810577279916498307130264816119062480395788963919822889673835039577697d = 3174124676656013174258111656715518261932447407587587373745001204373630194465607583875038303157271540416614763810219408013310091241179227844814818997567768quaternion = 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 = 12279853477283008553357657492789763255038870106301916103237655771107240855639019740311384654049683518074767532668306533768786610265460170996795820601321909b = 11210493117462584846695225251401309306620235323150317045189710917149538613051572049177523790232361862309737553875527265480632012163444954592595707353201593c = 450430550110220511170706355346853127519361342092668926760254273216011478764445031538810577279916498307130264816119062480395788963919822889673835039577697d = 3174124676656013174258111656715518261932447407587587373745001204373630194465607583875038303157271540416614763810219408013310091241179227844814818997567768
a_prime = 2265462378761600430532145400101912094563084708128292610022357820427900202076518621271341061446425878932435592087810301613872104317791934633397153003692733b_prime = 11782169557857342336271967041629416683561191223952219855027739818522859391187666351078682423218210372662669230179370323154138802086643543387102521811129172c_prime = 10908162276945008648926361072155772044902584979394235365102141792914012158138256920012734972335441493996292123708617034711883958615075943172083534030307874d_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/pythonimport 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 sysimport time
from gf2bv import LinearSystemfrom gf2bv.crypto.mt import MT19937from pwn import context, remotefrom z3 import BitVec, BitVecVal, LShR, Solver, sat
HOST, PORT = "nc1.ctfplus.cn", 24940WORDS, WORD_BITS = 624, 32BITS_PER_QUERY = 2NEED = WORDS * WORD_BITS // BITS_PER_QUERYTOTAL_QUERIES = 10000N, M = 624, 397MATRIX_A = 0x9908B0DFUPPER_MASK = 0x80000000LOWER_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 flagfrom utils import AESimport 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,getenvfrom AES import AESimport signalimport 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 reimport subprocessfrom pwn import remote
HOST, PORT = "nc1.ctfplus.cn", 49403MIN_UNIQUE = 100OUTFILE = "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,environfrom pwn import xorimport 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?
在这里面 uv 是比较小的,可以直接 copper 出来,然后再回溯求 seed,然后求 key,这里后面的步骤 ai 写的,我写前面的 copper
exp.py
import itertoolsfrom pwn import remotefrom sage.all import *
HOST = "nc1.ctfplus.cn"PORT = 24852
p = 76884956397045344220809746629001649093037950200943055203735601445031516197751a = 56698187605326110043627228396178346077120614539475214109386828188763884139993b = 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/ Some information may be outdated