Files
ctf/2026/cscg/rev/connivance/recover_flag.py
2026-03-24 00:35:37 +01:00

94 lines
3.8 KiB
Python

import hashlib
import time
def solve_flag(target_blob: bytes):
"""
Reverses the 8-stage hashing loop by walking backwards from the 7th shift
down to the 0th shift, recovering 1 bit of the flag per iteration.
"""
# 1. Setup Known Constraints
known_prefix = b"dach2026{"
known_suffix = b"}"
total_len = 29
# Calculate indices for the 19 unknown characters
unknown_indices = list(range(len(known_prefix), total_len - len(known_suffix)))
# Pre-calculate the bitwise NOT of the known parts
full_known_state = bytearray(total_len)
for i, b in enumerate(known_prefix):
full_known_state[i] = ~b & 0xFF
full_known_state[total_len - 1] = ~known_suffix[0] & 0xFF
# Split the target blob into the 8 independent target hashes
target_hashes = set(target_blob[i*32 : (i+1)*32] for i in range(8))
# This array will accumulate our recovered bits for the 19 unknown bytes
# By the end, it will contain the fully unshifted, inverted bytes.
recovered_unknowns = [0] * len(unknown_indices)
print("[*] Starting Bit-by-Bit Backtracking Attack...")
start_time = time.time()
# 2. Walk backwards from Shift 7 down to Shift 0
for shift_amount in range(7, -1, -1):
print(f"\n[*] Cracking Shift Layer {shift_amount} (Guessing bit {shift_amount} of payload)...")
# Pre-build the known parts of the state for this specific shift layer
base_state = bytearray(total_len)
for i in range(total_len):
if i < len(known_prefix) or i == total_len - 1:
base_state[i] = full_known_state[i] >> shift_amount
match_found = False
# Brute-force the next bit for all 19 unknown bytes (2^19 combinations = 524,288)
# This takes a few seconds per layer in pure Python.
for guess in range(1 << len(unknown_indices)):
test_state = bytearray(base_state)
# Inject our guessed bits into the test state
for i, idx in enumerate(unknown_indices):
guess_bit = (guess >> i) & 1
test_state[idx] = (recovered_unknowns[i] << 1) | guess_bit
# Hash and check against the pool of valid target hashes
if hashlib.sha256(test_state).digest() in target_hashes:
print(f" [+] Match found! Extracted bits: {bin(guess)[2:].zfill(19)}")
# Commit the guessed bits to our recovered array
for i in range(len(unknown_indices)):
recovered_unknowns[i] = (recovered_unknowns[i] << 1) | ((guess >> i) & 1)
match_found = True
break
if not match_found:
print(f" [-] CRITICAL FAILURE: Could not find a valid bit permutation at layer {shift_amount}.")
print(" Double check the dragonfly.bin data and prefix.")
return None
# 3. Final Assembly
# We now have the complete, unshifted inverted state.
final_inverted_state = bytearray(full_known_state)
for i, idx in enumerate(unknown_indices):
final_inverted_state[idx] = recovered_unknowns[i]
# Un-invert it to reveal the plaintext flag
final_flag = bytes(~b & 0xFF for b in final_inverted_state)
elapsed = time.time() - start_time
print(f"\n[+] Exploit completed in {elapsed:.2f} seconds.")
print(f"[!] RECOVERED FLAG: {final_flag.decode('ascii', errors='ignore')}")
return final_flag
# ==========================================
# Execution / Test Mock
# ==========================================
if __name__ == "__main__":
with open("./romfs/dragonfly.bin", "rb") as f:
target_blob = f.read()
solve_flag(target_blob)