CPUID
CPUID
Platform: spbctf | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-07-02 | Status: Solved Techniques: byte_permutation_reversing, cpuid_vendor_string_extraction, disassembly, qemu_cpuid_vendor_override, static_analysis, unicorn_emulation
Summary
Task: ELF64 PIE reverse challenge that reads the CPU vendor via CPUID leaf 0 twice (with a 'swap the processor' prompt in between) and permutes the vendor-string bytes into a 31-char flag. Solution: reverse the fixed byte permutation from the disassembly of main and feed AuthenticAMD (1st read) then GenuineIntel (2nd read) to reproduce the flag; verified with a Python simulator, Unicorn CPUID hook, and QEMU vendor override.
CPUID — spbctf
Description
Generating flag... Half flag done, please insert other processor and press ENTER... OK, flag generated. If it looks like meaningful phrase, it's what you need:
English summary: cpuid.elf is an ELF64 PIE (not stripped, compiled from cpuid.c, C + inline ASM). It prints Generating flag..., sleeps, reads the CPU vendor string via the CPUID instruction (leaf 0), and builds the first half of the flag. It then prints Half flag done, please insert other processor and press ENTER... and blocks on scanf("%c"), waiting for the user to "swap" the CPU. After the keypress it reads CPUID again, builds the second half, and prints the assembled 31-char string.
The joke: you cannot physically hot-swap a CPU mid-process, so the "flag" is the deterministic combined output of two different vendor CPUs — the intended pair is AuthenticAMD (first read) then GenuineIntel (second read).
Analysis
CPUID leaf 0 vendor string layout
mov eax, 0; cpuid returns the 12-byte vendor string spread across three 32-bit registers as little-endian dwords:
EBX = v[0:4],EDX = v[4:8],ECX = v[8:12]
So for the two relevant vendors:
| Vendor | EBX | EDX | ECX |
|---|---|---|---|
GenuineIntel | Genu | ineI | ntel |
AuthenticAMD | Auth | enti | cAMD |
| |
The byte permutation
Disassembling main with objdump -d -M intel (relevant range ~0x7ce–0x88c) shows the flag is assembled by a fixed byte permutation. Each source byte is identified by a register-label plus a byte index (0..3), where B=EBX, D=EDX, C=ECX. The assembly performs this with stosb/stosw/shr/shl/bswap on eax reloaded from ebx/ecx/edx.
First half — output[0..13], sourced from CPU #1:
C2 D1 D0 C3 D0 D2 D3 B0 B1 D2 D3 C0 D2 D3
Second half — output[14..30], sourced from CPU #2:
b0 b2 b3 c3 d0 b0 d1 d0 c3 b3 d0 b3 b0 b1 c0 c1 b3
(31 bytes total.)
Verification (three independent methods)
- Manual byte-label simulation in Python (below).
- Unicorn full emulation of
mainwith a CPUID hook that injects chosenEBX/ECX/EDXper call. - Running the real binary under Docker
linux/amd64: on Apple Silicon, Rosetta reportsGenuineIntelto the x86 binary, which printed exactlyenilieIGeeIneIGnuliGniluiuGentu— matching theGenuineIntel, GenuineIntelmodel. Vendor control was demonstrated withqemu-x86_64 -cpu qemu64,vendor=GenuineIntel ./cpuid.elfandvendor=AuthenticAMD(QEMU TCG default vendor isAuthenticAMD).
Solution
Since a real CPU cannot be swapped during one process run, the fix is to make the two CPUID reads return different vendors: AMD for the first call, Intel for the second. Reversing the permutation and feeding AuthenticAMD then GenuineIntel reproduces the flag directly.
Solve script
python read-only • copy allowed#!/usr/bin/env python3 # Reproduce the CPUID flag from the reverse-engineered permutation. first = ['C2','D1','D0','C3','D0','D2','D3','B0','B1','D2','D3','C0','D2','D3'] second = ['b0','b2','b3','c3','d0','b0','d1','d0','c3','b3','d0','b3','b0','b1','c0','c1','b3'] # register label -> offset into the 12-byte vendor string; index digit is 0..3 idx = lambda l: {'B': 0, 'D': 4, 'C': 8}[l[0].upper()] + int(l[1]) # v1 = vendor read by CPU #1 (first half), v2 = vendor read by CPU #2 (second half) build = lambda v1, v2: ''.join(v1[idx(l)] for l in first) + ''.join(v2[idx(l)] for l in second) print(build("AuthenticAMD", "GenuineIntel")) # REDACTED
All four Intel/AMD combinations (reference)
| CPU #1 | CPU #2 | Output |
|---|---|---|
| GenuineIntel | GenuineIntel | enilieIGeeIneIGnuliGniluiuGentu |
| GenuineIntel | AuthenticAMD | enilieIGeeIneIAthDeAneDhehAucAh |
| AuthenticAMD | GenuineIntel | REDACTED ← flag |
| AuthenticAMD | AuthenticAMD | MneDetiAutictiAthDeAneDhehAucAh |
| |
Real-binary alternative
Instead of simulating the permutation, you can run the real binary and force the two vendor reads:
- Inject vendors per-call with a CPUID hook (Unicorn emulation, or a
ptrace-based single-step that rewritesEBX/ECX/EDXafter eachcpuid— return AMD dwords on the 1st call, Intel dwords on the 2nd). - For a single-vendor sanity check,
qemu-x86_64 -cpu qemu64,vendor=GenuineIntel ./cpuid.elf(orvendor=AuthenticAMD) overrides the vendor the whole run; combine with a per-call hook to get the mixed AMD→Intel pair.