cleanup
This commit is contained in:
95
2026/cscg/rev/gloomweaver/solve.py
Normal file
95
2026/cscg/rev/gloomweaver/solve.py
Normal file
@@ -0,0 +1,95 @@
|
||||
# solve.py
|
||||
import numpy as np
|
||||
|
||||
# Load tables
|
||||
matrix_raw = open("/tmp/matrix_1253.bin", "rb").read()
|
||||
table_a = open("/tmp/table_2605.bin", "rb").read() # 55 bytes
|
||||
table_b = open("/tmp/table_2697.bin", "rb").read() # 55 bytes
|
||||
expected_raw = open("/tmp/expected_2119.bin", "rb").read() # 110 bytes
|
||||
|
||||
# Build coefficient matrix mod 256
|
||||
# coeff[i][j] = ((table_a[i] * table_b[j] * 1337) & 0xFF + matrix[i*55+j]) & 0xFF
|
||||
M = np.zeros((55, 55), dtype=np.int64)
|
||||
for i in range(55):
|
||||
for j in range(55):
|
||||
ab = table_a[i] * table_b[j] # 8x8 -> 16-bit
|
||||
val = (ab * 1337) & 0xFF # imul then take low byte
|
||||
val = (val + matrix_raw[i * 55 + j]) & 0xFF # add matrix element
|
||||
M[i][j] = val
|
||||
|
||||
# Expected values — try byte extraction (every other byte = low bytes of words)
|
||||
# The cmpsw comparison uses words, so expected is 55 x 2-byte values
|
||||
# Result bytes are at rsp[0..54], compared as words: (rsp[0],rsp[1]), (rsp[2],rsp[3])...
|
||||
# Since only odd-indexed bytes have values, let's try both interpretations
|
||||
|
||||
# Interpretation 1: expected is just the first 55 bytes
|
||||
expected_bytes = list(expected_raw[:55])
|
||||
|
||||
# Interpretation 2: expected is every other byte (word low bytes)
|
||||
expected_words_lo = [expected_raw[i*2] for i in range(55)]
|
||||
|
||||
print("Expected (first 55 bytes):", [hex(x) for x in expected_bytes[:10]])
|
||||
print("Expected (word lo bytes):", [hex(x) for x in expected_words_lo[:10]])
|
||||
|
||||
# Gaussian elimination mod 256
|
||||
def solve_mod256(M, b):
|
||||
n = len(b)
|
||||
# Augmented matrix
|
||||
aug = np.zeros((n, n+1), dtype=np.int64)
|
||||
for i in range(n):
|
||||
for j in range(n):
|
||||
aug[i][j] = M[i][j] % 256
|
||||
aug[i][n] = b[i] % 256
|
||||
|
||||
# Forward elimination
|
||||
for col in range(n):
|
||||
# Find pivot with odd value (invertible mod 256 requires gcd(pivot,256)=1)
|
||||
pivot = -1
|
||||
for row in range(col, n):
|
||||
if aug[row][col] % 2 == 1: # odd = invertible mod 256
|
||||
pivot = row
|
||||
break
|
||||
if pivot == -1:
|
||||
print(f"No odd pivot at column {col}, trying any nonzero...")
|
||||
for row in range(col, n):
|
||||
if aug[row][col] != 0:
|
||||
pivot = row
|
||||
break
|
||||
if pivot == -1:
|
||||
print(f"Singular at column {col}")
|
||||
return None
|
||||
|
||||
# Swap
|
||||
aug[[col, pivot]] = aug[[pivot, col]]
|
||||
|
||||
# Compute inverse of pivot mod 256
|
||||
pv = int(aug[col][col]) % 256
|
||||
inv = pow(pv, -1, 256) if pv % 2 == 1 else None
|
||||
if inv is None:
|
||||
print(f"Non-invertible pivot {pv} at col {col}")
|
||||
return None
|
||||
|
||||
# Scale pivot row
|
||||
aug[col] = (aug[col] * inv) % 256
|
||||
|
||||
# Eliminate
|
||||
for row in range(n):
|
||||
if row != col and aug[row][col] != 0:
|
||||
factor = int(aug[row][col])
|
||||
aug[row] = (aug[row] - factor * aug[col]) % 256
|
||||
|
||||
solution = aug[:, n] % 256
|
||||
return solution
|
||||
|
||||
# Try both interpretations
|
||||
for name, expected in [("first_55_bytes", expected_bytes),
|
||||
("word_lo_bytes", expected_words_lo)]:
|
||||
print(f"\n=== Trying {name} ===")
|
||||
sol = solve_mod256(M, expected)
|
||||
if sol is not None:
|
||||
flag_content = bytes([int(x) for x in sol])
|
||||
flag = b"CTF{th1s" + flag_content + b"}"
|
||||
printable = all(0x20 <= b < 0x7f for b in flag_content)
|
||||
print(f"Solution: {flag}")
|
||||
print(f"Printable: {printable}")
|
||||
print(f"Hex: {flag_content.hex()}")
|
||||
Reference in New Issue
Block a user