96 lines
3.4 KiB
Python
96 lines
3.4 KiB
Python
# 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()}")
|