← Back to Writeups
HTBN/AReversing

loicense

XESXOR8/23/20264 min read
#reversing#htb#n/a

loicense

Platform: Pingctf | Category: Reversing | Type: Challenge | Difficulty: Hard | OS: NA | Author: D3v0o0Nu11 | Date: 2026-04-19 | Status: Solved Techniques: ieee754_encoding, linear_system_solving, metadata_bruteforce

Summary

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.

Recon

Port scan

nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: pingCTF | ID: 20260419_pingctf_loicense
  • Tags: linear_algebra, elf, keygen, ieee754, float32, dwarf
  • Indicators: DWARF debug info in binary, 99-char HWID split into 9 rows of 11, key format with metadata prefix + hex floats, 10x10 linear system validation
  • Source: 20260419_pingctf_loicense.md

Foothold

Vulnerability / Misconfiguration

  1. Ieee754_encoding
  2. Linear_system_solving
  3. Metadata_bruteforce
<command>

Exploitation

  • See original writeup content for detailed exploitation.

Privilege Escalation

Enumeration

sudo -l
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
cat /etc/crontab
ps aux

Exploitation

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

Key Takeaways / Lessons

  • ieee754_encoding
  • linear_system_solving
  • metadata_bruteforce
  • Tags: linear_algebra, elf, keygen, ieee754, float32, dwarf

Original Writeup

<details><summary>Click to expand original content</summary>

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:

  1. Each HWID row provides coefficients: first 10 chars = coefficients, 11th char = RHS
  2. The metadata row (from key) provides the 10th equation
  3. Coefficients are raw ASCII values of characters (e.g., 'A' = 65, '0' = 48)
  4. The 10 float values from the key must satisfy all 10 equations

The system validates: A @ signature = b where:

  • A is 10x10 matrix from ASCII values of first 10 chars per row
  • b is 10-element vector from ASCII values of 11th char per row
  • signature is the 10 float values decoded from the key

Solution

Keygen Strategy

  1. Parse HWID into 9 rows of 11 ASCII values each
  2. Try multiple metadata candidates to find one that yields a well-conditioned 10x10 system
  3. Solve the linear system using numpy
  4. Encode solution floats as IEEE-754 hex
  5. 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
</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR