Skip to content

Repository files navigation

ub86-pred - documenting and demonstrating undefined x86 effects as opaque state

A naive, easy-to-follow sample to check whether your analysis tool (decoders, lifters, emulators) models undefined effects correctly across these opaque instructions.

Two codegen paths, same runtime behavior:

  • Static (default), stubs from ub_stubs.gen.asm, maze from chain.gen.cpp (both generated and committed).
  • JIT (-DUB86_USE_ASMJIT=ON), asmjit emits stubs + maze into a PAGE_EXECUTE_READ region at startup with per-process random tail-jmps (push/ret, lea+jmp, decoy bytes after jmp); chain runs on a fresh std::thread.

Design notes in devdocs/plans.md; changelog in devdocs/progress.md. (thanks @mrexodia for this tip!)

Build

Windows + MSVC x64, Ninja optional.

build.bat                # static path  -> build/toy.exe
build-jit.bat            # JIT path     -> build-jit/toy.exe (UB86_USE_ASMJIT=ON)

Both scripts auto-detect MSVC via vswhere.exe (ships with every VS 2017+ install). If it's not on a default path, set VCVARS to your vcvars64.bat to override, or run from an x64 Native Tools Command Prompt:

set "VCVARS=C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
build.bat

Run

toy.exe                     # all probes + branchers (default)
toy.exe cpuid               # CPU vendor / brand / family-model-stepping
toy.exe ctt-chain           # prompt for key, walk the maze, print success|fail
toy.exe probe-self [PATH]   # observe truths, write back to stubs.gen.json
toy.exe use-affinity        # split probes/branchers across E vs P cores

ctt-chain is the lifting/emulation toy. It reads 8 ASCII chars and checks a password. The code itself isn't interesting; the point is whether your tool can either (1) recover a path to the success condition, or (2) lift + optimize the program down to a form where the opaque CPU state is obviously irrelevant.

Regen flow

tools/gen_stubs.py is the source of truth: it emits the manifest (tools/stubs.gen.json), the asm stubs, the shared header, and a placeholder chain.gen.cpp so the first build links. tools/gen_chain.py later overwrites chain.gen.cpp with the real maze from the recorded truths. Truths come from toy.exe probe-self (works in both modes) and are folded back via gen_stubs.py --record-truth.

Static path:

python tools/gen_stubs.py --seed 12345        # stubs + placeholder chain
build.bat                                     # bootstrap (truths still null)
build/toy.exe probe-self                      # fill truths in stubs.gen.json
python tools/gen_stubs.py --record-truth      # re-emit header with truths
python tools/gen_chain.py                     # emit the real chain.gen.cpp
build.bat                                     # final build
printf 'ub86pred\n' | build/toy.exe ctt-chain # -> success

JIT path (no gen_chain.py, chain_jit.cpp emits the maze at runtime from the same manifest):

python tools/gen_stubs.py --seed 12345
build-jit.bat
build-jit/toy.exe probe-self
python tools/gen_stubs.py --record-truth
build-jit.bat
printf 'ub86pred\n' | build-jit/toy.exe ctt-chain  # -> success

Run on a different uarch and probe-self records that host's truths; both codegen paths consume them. Stubs that fault (aad_imm_*) stay in the corpus but are excluded from the chain.

CLI

tools/gen_stubs.py: --seed N (default 0xC0FFEE), --instances-per-primitive N (default 2, pool ≈ 14 × N), --record-truth (re-emit header from manifest), --no-placeholder-chain, --out-asm/--out-hpp/--out-chain/--manifest PATH.

tools/gen_chain.py: --depth N (default 1024, ≥ 2), --manifest/--out PATH.

Primitives (14)

# Primitive UB axis
1 imul_zf imul r32,r32: SF/ZF/AF/PF undefined
2 mul_zf mul r32: SF/ZF/AF/PF undefined
3 idiv_flags idiv r32: CF/OF/SF/ZF/AF/PF undefined
4 bsf_dest_zero bsf r32,0: destination undefined
5 bsr_dest_zero bsr r32,0: destination undefined
6 bswap16_high 66 bswap r16: result undefined
7 shld_overshoot shld r,r,cl>31: result + flags undefined
8 shrd_overshoot shrd r,r,cl>31: result + flags undefined
9 lzcnt_zero lzcnt: SF/PF/AF/OF undefined
10 tzcnt_zero tzcnt: SF/PF/AF/OF undefined
11 bextr_pf bextr: PF/AF/SF undefined
12 rcpss_lowbit rcpss xmm: low mantissa bit varies (AMD vs Intel)
13 rsqrtss_lowbit rsqrtss xmm: low mantissa bit varies
14 aad_imm D5 imm8: #UD in long mode (faults)

Scraper

Per-instruction Intel SDM JSON in scripts/data/intel/ (committed). Regenerate or browse via scripts/scrape_intel_pdf_entries.py + scripts/serve_intel_browser.py, see SCRAPER.md.

Credits

About

A toy project/CTF analyzing quirks on x86 CPUs

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages