loicense
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.
$ ls tags/ techniques/
loicense - pingCTF
Description
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.
Analysis
Binary Structure
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
Key Format (from decode_signature_from_key)
License key structure (91 characters total):
- 11 chars: Metadata prefix (alphanumeric:
0-9A-Z) - 80 chars: 10 IEEE-754 float32 values encoded as 8 hex digits each
HWID Structure
The 99-character HWID (charset 0-9A-Z) is split into 9 rows of 11 characters each.
Validation Model (from compute_system_loss / probe_signature_candidate)
The validation constructs a 10x10 linear system:
- Each HWID row provides coefficients: first 10 chars = coefficients, 11th char = RHS
- The metadata row (from key) provides the 10th equation
- Coefficients are raw ASCII values of characters (e.g.,
'A'= 65,'0'= 48) - The 10 float values from the key must satisfy all 10 equations
The system validates: A @ signature = b where:
Ais 10x10 matrix from ASCII values of first 10 chars per rowbis 10-element vector from ASCII values of 11th char per rowsignatureis the 10 float values decoded from the key
Solution
Keygen Strategy
- Parse HWID into 9 rows of 11 ASCII values each
- Try multiple metadata candidates to find one that yields a well-conditioned 10x10 system
- Solve the linear system using numpy
- Encode solution floats as IEEE-754 hex
- Select metadata with smallest float32 residual for numerical stability
Solve Script
#!/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
Remote Interaction
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())
Metadata Bruteforce
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
Similar writeups
- [reverse][Pro]CrackMe - TaipanByte CTF— taipanbyte
- [reverse][Pro]s5.out— spbctf
- [reverse][Pro]Challenge7— tamuctf
- [web][Pro]OFD— spbctf
- [reverse][Pro]spbctf_4_x86_64— spbctf