$ cat writeup.md…
$ cat writeup.md…
pingCTF
Task: License keygen for SmartAttend ELF with DWARF debug info, validates keys via 10x10 linear system. Solution: Reverse key format (11-char metadata + 10 IEEE-754 floats as hex), solve linear system from HWID ASCII values, bruteforce metadata for numerical stability.
License validation challenge with SmartAttend ELF binary.
Given an ELF binary SmartAttend with DWARF debug info and a remote checker that sends 100 random HWIDs. For each HWID, generate a valid license key. Pass all 100 rounds to get the flag.
The binary is a 64-bit ELF with full DWARF debug symbols, making reverse engineering straightforward with radare2:
SmartAttend: ELF 64-bit LSB executable, x86-64, with debug_info, not stripped
decode_signature_from_key)License key structure (91 characters total):
0-9A-Z)The 99-character HWID (charset 0-9A-Z) is split into 9 rows of 11 characters each.
compute_system_loss / probe_signature_candidate)The validation constructs a 10x10 linear system:
'A' = 65, '0' = 48)The system validates: A @ signature = b where:
A is 10x10 matrix from ASCII values of first 10 chars per rowb is 10-element vector from ASCII values of 11th char per rowsignature is the 10 float values decoded from the key#!/usr/bin/env python3 import struct import numpy as np def build_key(hwid: str, meta_candidates: list[str]) -> tuple[str, float]: """Generate license key for given HWID.""" # Parse HWID into 9 rows of ASCII values rows = [ np.array([float(ord(c)) for c in hwid[i*11:(i+1)*11]], dtype=np.float64) for i in range(9) ] base_matrix = np.vstack([row[:10] for row in rows]) base_rhs = np.array([row[10] for row in rows], dtype=np.float64) best_key, best_residual = None, float("inf") for meta in meta_candidates: # Add metadata as 10th row meta_row = np.array([float(ord(c)) for c in meta], dtype=np.float64) matrix = np.vstack([base_matrix, meta_row[:10]]) rhs = np.append(base_rhs, meta_row[10]) try: signature = np.linalg.solve(matrix, rhs).astype(np.float32) except np.linalg.LinAlgError: continue # Check residual after float32 rounding residual = float(np.max(np.abs(matrix @ signature.astype(np.float64) - rhs))) if residual < best_residual: # Encode floats as IEEE-754 hex key = meta + "".join( f"{struct.unpack('>I', struct.pack('>f', float(v)))[0]:08X}" for v in signature ) best_key, best_residual = key, residual if residual < 1e-6: break return best_key, best_residual
Connect to remote, receive 100 HWIDs, generate and send keys:
for round_idx in range(100): hwid = recv_hwid(sock) # 99-char alphanumeric key, residual = build_key(hwid, META_CANDIDATES) sock.sendall((key + "\n").encode())
To ensure numerical stability across 100 random HWIDs, generate ~500 metadata candidates and pick the one with smallest residual:
def make_meta_candidates() -> list[str]: rng = random.Random(1337) alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" seeds = ["10000000000", "1234567890A", ...] for _ in range(500): seeds.append(rng.choice("0123456789") + "".join(rng.choice(alphabet) for _ in range(10))) return seeds
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar