Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

libcaptcha/quickjs-wasm

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

quickjs-wasm

Polymorphic QuickJS WASM virtual machine. Executes encrypted JavaScript bytecode in a sandboxed, anti-debug environment. Responses are encrypted and signed. Every build produces a unique binary.

How It Works

sequenceDiagram
    participant S as Server
    participant B as Browser
    participant W as WASM VM

    S->>B: vm.js + vm.wasm + loader.js (unique per build)
    S->>B: challenge.vmbc (encrypted bytecode)
    B->>W: vm_init()
    B->>W: vm_exec_bytecode(bundle)
    W->>W: ChaCha20 decrypt (KEY_DECRYPT)
    W->>W: QuickJS eval bytecode
    W->>W: ChaCha20 encrypt result (KEY_ENCRYPT)
    W->>W: HMAC-SHA256 sign (KEY_SIGN)
    W-->>B: encrypted + signed response
    B->>S: forward response
    S->>S: verify HMAC + decrypt (keys from manifest.json)
Loading

VM Architecture

graph TD
    subgraph "WASM Binary (unique per build)"
        B[vm_bridge.c<br/>init / exec / destroy]
        C[vm_crypto.c<br/>ChaCha20 · SHA-256 · HMAC]
        A[vm_antidbg.c<br/>timing · sequence · integrity]
        K[vm_keys.h<br/>3 real keys hidden in decoy arrays]
        D[vm_deadcode.c<br/>20-40 fake functions]
        FS[vm_fake_strings.c<br/>decoy string table]
        FST[vm_fake_state.c<br/>decoy state machine]
    end

    B --> C
    B --> A
    B --> K
    B -.-> D
    B -.-> FS
    B -.-> FST

    subgraph "Generated per build"
        R1[vm_exports.h<br/>export name shuffling]
        R2[vm_*_rename.h<br/>symbol renaming]
        R3[vm_cflow.h<br/>control flow flattening]
    end

    R1 --> B
    R2 --> B
    R3 --> B
Loading

Three Embedded Keys

Baked into each WASM binary at compile time, hidden at random offsets inside decoy byte arrays:

Key Purpose
VM_KEY_DECRYPT ChaCha20-decrypts incoming .vmbc bundles
VM_KEY_ENCRYPT ChaCha20-encrypts JS execution results
VM_KEY_SIGN HMAC-SHA256 signs encrypted responses

The server receives build/manifest.json with matching keys for verification.

Wire Formats

Bytecode bundle (.vmbc):

┌──────────┬──────────────┬──────────┬────────────┐
│ 4B magic │ 4B bc_len LE │ 12B nonce│ ciphertext │
│ "VMBC"   │              │          │            │
└──────────┴──────────────┴──────────┴────────────┘

Response:

┌──────────┬───────────────┬───────────┬────────────┬──────────┐
│ 4B magic │ 4B total_len  │ 12B nonce │ ciphertext │ 32B HMAC │
│ "VMRP"   │ LE            │           │            │          │
└──────────┴───────────────┴───────────┴────────────┴──────────┘

Polymorphic Builds

Each node scripts/build.js invocation produces a structurally unique binary:

  • New keys — fresh 32-byte random key material
  • Randomized memory layout — 3 real + 1 trap + 3 decoy keys shuffled across variable-size arrays (384-1024B) with double-indirection macros
  • Dead code injection — 20-40 functions with realistic bodies, fake string tables, fake state machines
  • Full symbol renaming — all exports and internal symbols renamed via -include headers with random prefixes
  • Control flow obfuscation — dispatch macros, opaque predicates via volatile globals
  • Build entropy — random BUILD_SEED + BUILD_ENTROPY_* defines alter compilation

Anti-Debug

The VM gates every execution through antidbg_check():

  • Call sequence — must init before exec
  • Execution counter — capped at 64 per session
  • Timing anomaly — >120s between execs triggers failure
  • Integrity hashing — FNV-1a accumulator over internal state
  • Poison latch — once tripped, VM permanently refuses execution
  • Symbol obfuscation — all names randomized per build

JS introspection: __vm_ts(), __vm_integrity(), __vm_check().

Project Structure

src/
  vm_bridge.c          Entry points: init, destroy, exec, free
  vm_crypto.c/h        ChaCha20, SHA-256, HMAC-SHA256
  vm_antidbg.c/h       Timing, call sequence, integrity
scripts/
  build.js             Polymorphic build orchestrator
  compile.js           JS → encrypted .vmbc bundles
build/                 (generated per build)
  manifest.json        Keys + export map (server-side)
web/
  index.html           Test page
  hello.js             Minimal test bytecode source
  loader.js            (generated) Build-matched WASM loader
  vm.js/vm.wasm        (generated) Emscripten output

Prerequisites

  • Emscripten SDK
  • Node.js 18+
  • QuickJS source (git submodule update --init)

Usage

git submodule update --init

source /path/to/emsdk/emsdk_env.sh

# Build (polymorphic — each run produces unique output)
node scripts/build.js

# Compile JS to encrypted bytecode
node scripts/compile.js challenge.js
node scripts/compile.js challenge.js --key <hex>

# Test
node scripts/compile.js web/hello.js --out web/test.vmbc
# Open web/index.html

Output

File Purpose
web/vm.js + web/vm.wasm Emscripten WASM module
web/loader.js Build-matched JS loader
build/manifest.json Keys + export map (keep server-side)
build/*.vmbc Encrypted bytecode bundles

Formatting

npx prtfm
clang-format -i src/*.c src/*.h

License

MIT

About

Polymorphic QuickJS WASM virtual machine. Executes encrypted JavaScript bytecode in a sandboxed, anti-debug environment. Responses are encrypted and signed. Every build produces a unique binary.

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.