Skip to content

begoon/nor

Repository files navigation

NORCPU Hackme Challenge

A hackme puzzle built on a one-instruction CPU (OISC) that uses only the NOR operation. Originally created by Alexander Demin in 2011, based on the NORCPU concept by Alexander Peslyak.

How it works

The project is a compiler and emulator for a virtual CPU whose only instruction is NOR (bitwise NOT-OR on two 16-bit words). All higher-level operations — MOV, ADD, XOR, PUSH/POP, CALL/RET, branching — are built as macros that expand into sequences of NOR instructions.

The script:

  1. Compiles a password-checking program from macro calls into a flat array of 16-bit words (code + data in a single address space, max 65535 words).
  2. Runs the program in the built-in emulator to verify correctness.
  3. Emits a self-contained HTML/JavaScript page (norcpu.html) that embeds the compiled memory image and runs the same NOR CPU in the browser.

The challenge: given only the HTML page, figure out the password that produces the secret magic message.

Project structure

  • cpu.py — NOR CPU primitives: all macros (NOR, MOV, ADD, XOR, MUL3, etc.), architectural registers, and code/data segment management. Imported with from cpu import * by program modules.
  • program_hash.py — hash-based password check (one-way, brute-force only). Folds all input characters into a 16-bit accumulator via acc = (acc + ch) * 3 + K, then compares against a compile-time expected hash.
  • program_xor.py — original XOR per-character password check (reversible). Each input character is XORed with a rolling mask and compared against a stored encoded password.
  • norcpu.py — compiler, emulator, CLI entry point, and HTML generator. Selects the program algorithm via -a flag.
  • norcpu_test.py — pytest tests covering both algorithms.

Running

# Generate both algorithms
just generate

# Or individually
just hash
just xor

# Direct invocation
uv run norcpu.py --password "MySecret" --secret-code "You win!" -a hash
uv run norcpu.py --password "MySecret" --secret-code "You win!" -a xor

This compiles the program, performs a test run to verify the password produces the expected secret code, and generates:

  • norcpu.asm — the compiled assembly listing
  • norcpu-{algo}.html — the standalone hackme challenge page
  • norcpu-{algo}-visual.html — memory access visualizer (256x256 grid of cells)

Algorithms

hash (default)

The password is not stored in memory. Instead, a one-way hash is computed at compile time and embedded as an immediate value. At runtime, the same hash is computed over user input and compared. Knowing the algorithm does not help recover the password — only brute force works.

acc = seed
for ch in input:
    acc = (acc + ch) & 0xFFFF
    acc = (acc * 3 + K) & 0xFFFF
if acc != expected_hash or len(input) != expected_len:
    fail

xor (original)

Both the password and the secret code are stored in memory pre-encoded: each character is XORed with a rolling 16-bit mask. The password can be recovered by reversing the XOR operations.

mask = password.xor_mask
cmp_flag = 0
for i in range(len(password)):
    ch = input[i] ^ mask
    stored = encoded_password[i]         # == password[i] ^ mask
    cmp_flag |= ch ^ stored              # zero iff input[i] == password[i]
    mask = (mask * 3 + password.add_const) & 0xFFFF

Phase 2 (both algorithms)

On correct password, the secret code is decrypted by XORing stored bytes with a rolling mask (double XOR recovers plaintext):

mask = code.xor_mask
for i in range(len(code)):
    output[i] = encoded_code[i] ^ mask
    mask = (mask * 3 + code.add_const) & 0xFFFF

Visualizer

Download and open in a browser:

The visualizer shows a 256x256 grid where each cell represents one of the 65536 memory addresses. As the CPU executes, cells are colored by access type:

  • Blue — instruction fetch (cells at i, i+1, i+2); darker shade means more repeated accesses (hot loops appear nearly black)
  • Green — operand read (cells at addresses a, b)
  • Yellow — result write (cell at address r)
  • Red — the exit register cell when execution halts

With a wrong password only ~25% of memory lights up (comparison loop only). With the correct password ~99.7% is reached (both comparison and decoding loops).

Testing

uv run pytest

About

One-instruction CPU (OISC) NOR

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Languages