94 lines
3.8 KiB
Python
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)
|