mirror of
https://github.com/lukaszraczylo/go-obfuscation.git
synced 2026-10-12 05:32:33 +00:00
560 lines
14 KiB
Go
560 lines
14 KiB
Go
package vm
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestComputeExpr(t *testing.T) {
|
|
// Compute (10 + 20) * 3 = 90
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{10}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{20}}},
|
|
{Instr: Instruction{Op: OpAdd}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{3}}},
|
|
{Instr: Instruction{Op: OpMul}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 90 {
|
|
t.Errorf("expected 90, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestSubtraction(t *testing.T) {
|
|
// 100 - 37 = 63
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{100}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{37}}},
|
|
{Instr: Instruction{Op: OpSub}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 63 {
|
|
t.Errorf("expected 63, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestDivision(t *testing.T) {
|
|
// 84 / 4 = 21
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{84}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{4}}},
|
|
{Instr: Instruction{Op: OpDiv}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 21 {
|
|
t.Errorf("expected 21, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestModulo(t *testing.T) {
|
|
// 17 % 5 = 2
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{17}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{5}}},
|
|
{Instr: Instruction{Op: OpMod}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 2 {
|
|
t.Errorf("expected 2, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestBitwiseOps(t *testing.T) {
|
|
// (0xFF XOR 0x0F) AND 0xF0 = 0xF0
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{0xFF}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{0x0F}}},
|
|
{Instr: Instruction{Op: OpXor}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{0xF0}}},
|
|
{Instr: Instruction{Op: OpAnd}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 0xF0 {
|
|
t.Errorf("expected 0xF0, got 0x%X", result)
|
|
}
|
|
}
|
|
|
|
func TestShiftOps(t *testing.T) {
|
|
// 1 SHL 8 = 256
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{1}}},
|
|
{Instr: Instruction{Op: OpShl, Args: []int64{8}}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 256 {
|
|
t.Errorf("expected 256, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestComparisons(t *testing.T) {
|
|
// 5 < 10 → 1
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{5}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{10}}},
|
|
{Instr: Instruction{Op: OpCmpLt}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 1 {
|
|
t.Errorf("expected 1, got %d", result)
|
|
}
|
|
|
|
// 10 > 5 → 1
|
|
program = []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{10}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{5}}},
|
|
{Instr: Instruction{Op: OpCmpGt}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code = Compile(program)
|
|
vm = NewVM(code, nil)
|
|
result = vm.Run()
|
|
|
|
if result != 1 {
|
|
t.Errorf("expected 1, got %d", result)
|
|
}
|
|
|
|
// 7 == 7 → 1
|
|
program = []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{7}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{7}}},
|
|
{Instr: Instruction{Op: OpCmpEq}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code = Compile(program)
|
|
vm = NewVM(code, nil)
|
|
result = vm.Run()
|
|
|
|
if result != 1 {
|
|
t.Errorf("expected 1, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestJump(t *testing.T) {
|
|
// JMP to "end", skipping the 999 push
|
|
// PUSH 42
|
|
// JMP end
|
|
// PUSH 999 <- skipped
|
|
// end: HALT
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{42}}},
|
|
{Label: "", Instr: Instruction{Op: OpJmp}, Target: "end"},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{999}}},
|
|
{Label: "end", Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 42 {
|
|
t.Errorf("expected 42, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestJzJnz(t *testing.T) {
|
|
// JZ pops the test value; verify both branches work.
|
|
//
|
|
// Phase 1: JZ should take (value is 0)
|
|
// PUSH 0, JZ zero, PUSH 999 (skipped), zero: PUSH 77
|
|
// Phase 2: JNZ should take (value is 1)
|
|
// DUP, JNZ nz, PUSH 888 (skipped), nz: HALT
|
|
// Result: 77
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{0}}},
|
|
{Instr: Instruction{Op: OpJz}, Target: "zero"},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{999}}},
|
|
{Label: "zero", Instr: Instruction{Op: OpPush, Args: []int64{77}}},
|
|
{Instr: Instruction{Op: OpDup}},
|
|
{Instr: Instruction{Op: OpJnz}, Target: "nz"},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{888}}},
|
|
{Label: "nz", Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 77 {
|
|
t.Errorf("expected 77, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestStoreLoad(t *testing.T) {
|
|
// PUSH 10
|
|
// STORE 0 var[0] = 10
|
|
// PUSH 20
|
|
// STORE 1 var[1] = 20
|
|
// LOAD 0
|
|
// LOAD 1
|
|
// ADD var[0] + var[1] = 30
|
|
// HALT
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{10}}},
|
|
{Instr: Instruction{Op: OpStore, Args: []int64{0}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{20}}},
|
|
{Instr: Instruction{Op: OpStore, Args: []int64{1}}},
|
|
{Instr: Instruction{Op: OpLoad, Args: []int64{0}}},
|
|
{Instr: Instruction{Op: OpLoad, Args: []int64{1}}},
|
|
{Instr: Instruction{Op: OpAdd}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 30 {
|
|
t.Errorf("expected 30, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestDup(t *testing.T) {
|
|
// PUSH 5, DUP, MUL = 25
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{5}}},
|
|
{Instr: Instruction{Op: OpDup}},
|
|
{Instr: Instruction{Op: OpMul}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 25 {
|
|
t.Errorf("expected 25, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestSwap(t *testing.T) {
|
|
// PUSH 1, PUSH 2, SWAP 1, SUB = 2 - 1 = 1
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{1}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{2}}},
|
|
{Instr: Instruction{Op: OpSwap, Args: []int64{1}}},
|
|
{Instr: Instruction{Op: OpSub}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 1 {
|
|
t.Errorf("expected 1, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestRot(t *testing.T) {
|
|
// PUSH 1, PUSH 2, PUSH 3, ROT
|
|
// Stack after ROT: [3, 1, 2] (top=2)
|
|
// POP → 2, POP → 1, POP → 3
|
|
// We verify by: PUSH 1, PUSH 2, PUSH 3, ROT, SUB, SUB = 3 - 1 - 2 ... wait
|
|
// Actually ROT puts top to bottom: [1, 3, 2] with top = 2
|
|
// Let me just check stack ordering
|
|
// PUSH 10, PUSH 20, PUSH 30, ROT
|
|
// Before: [10, 20, 30] top=30
|
|
// After: top=20, then 10, then 30
|
|
// So: POP=20, POP=10, POP=30 → 20 - 10 = 10, 10 - 30 = ... let me just add
|
|
// PUSH 10, PUSH 20, PUSH 30, ROT, ADD, ADD = 20 + 10 + 30 = 60
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{10}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{20}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{30}}},
|
|
{Instr: Instruction{Op: OpRot}},
|
|
{Instr: Instruction{Op: OpAdd}},
|
|
{Instr: Instruction{Op: OpAdd}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 60 {
|
|
t.Errorf("expected 60, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestNot(t *testing.T) {
|
|
// NOT 0 = -1
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{0}}},
|
|
{Instr: Instruction{Op: OpNot}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != -1 {
|
|
t.Errorf("expected -1, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestCallout(t *testing.T) {
|
|
// PUSH 7, PUSH 3, PUSH 2 (arity), CALL 42, HALT
|
|
// callout(42) sums its args: 7 + 3 = 10
|
|
callouts := map[int]func([]int64) int64{
|
|
42: func(args []int64) int64 {
|
|
return args[0] + args[1]
|
|
},
|
|
}
|
|
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{7}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{3}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{2}}}, // arity
|
|
{Instr: Instruction{Op: OpCall, Args: []int64{42}}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, callouts)
|
|
result := vm.Run()
|
|
|
|
if result != 10 {
|
|
t.Errorf("expected 10, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestLoop(t *testing.T) {
|
|
// Compute sum 1..5 = 15 using a loop
|
|
// var[0] = accumulator = 0
|
|
// var[1] = counter = 5
|
|
// loop:
|
|
// LOAD 1 push counter
|
|
// JZ done
|
|
// LOAD 0 push acc
|
|
// LOAD 1 push counter
|
|
// ADD acc + counter
|
|
// STORE 0 acc = result
|
|
// LOAD 1
|
|
// PUSH 1
|
|
// SUB counter - 1
|
|
// STORE 1 counter = result
|
|
// JMP loop
|
|
// done:
|
|
// LOAD 0
|
|
// HALT
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{0}}},
|
|
{Instr: Instruction{Op: OpStore, Args: []int64{0}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{5}}},
|
|
{Instr: Instruction{Op: OpStore, Args: []int64{1}}},
|
|
{Label: "loop", Instr: Instruction{Op: OpLoad, Args: []int64{1}}},
|
|
{Instr: Instruction{Op: OpJz}, Target: "done"},
|
|
{Instr: Instruction{Op: OpLoad, Args: []int64{0}}},
|
|
{Instr: Instruction{Op: OpLoad, Args: []int64{1}}},
|
|
{Instr: Instruction{Op: OpAdd}},
|
|
{Instr: Instruction{Op: OpStore, Args: []int64{0}}},
|
|
{Instr: Instruction{Op: OpLoad, Args: []int64{1}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{1}}},
|
|
{Instr: Instruction{Op: OpSub}},
|
|
{Instr: Instruction{Op: OpStore, Args: []int64{1}}},
|
|
{Instr: Instruction{Op: OpJmp}, Target: "loop"},
|
|
{Label: "done", Instr: Instruction{Op: OpLoad, Args: []int64{0}}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 15 {
|
|
t.Errorf("expected 15, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestNopPadding(t *testing.T) {
|
|
// NOPs should be harmless padding
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpNop}},
|
|
{Instr: Instruction{Op: OpNop}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{42}}},
|
|
{Instr: Instruction{Op: OpNop}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 42 {
|
|
t.Errorf("expected 42, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestStepwiseExecution(t *testing.T) {
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{3}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{4}}},
|
|
{Instr: Instruction{Op: OpMul}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := CompileWith(program, CompileOpts{NoJunk: true})
|
|
vm := NewVM(code, nil)
|
|
|
|
steps := 0
|
|
for vm.Step() {
|
|
steps++
|
|
}
|
|
|
|
if steps != 3 {
|
|
t.Errorf("expected 3 steps, got %d", steps)
|
|
}
|
|
|
|
if vm.Halted() != true {
|
|
t.Error("expected VM to be halted")
|
|
}
|
|
|
|
if vm.SP() != 1 {
|
|
t.Errorf("expected SP=1, got %d", vm.SP())
|
|
}
|
|
}
|
|
|
|
func TestXorObfuscationDeterminism(t *testing.T) {
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{10}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{20}}},
|
|
{Instr: Instruction{Op: OpAdd}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{3}}},
|
|
{Instr: Instruction{Op: OpMul}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
opts := CompileOpts{NoJunk: true}
|
|
code1 := CompileWith(program, opts)
|
|
code2 := CompileWith(program, opts)
|
|
|
|
if len(code1) != len(code2) {
|
|
t.Fatalf("bytecode length differs: %d vs %d", len(code1), len(code2))
|
|
}
|
|
|
|
for i := range code1 {
|
|
if code1[i] != code2[i] {
|
|
t.Fatalf("bytecode non-deterministic at offset %d", i)
|
|
}
|
|
}
|
|
|
|
vm1 := NewVM(code1, nil)
|
|
vm2 := NewVM(code2, nil)
|
|
|
|
r1 := vm1.Run()
|
|
r2 := vm2.Run()
|
|
|
|
if r1 != r2 {
|
|
t.Errorf("results differ: %d vs %d", r1, r2)
|
|
}
|
|
}
|
|
|
|
func TestBytecodeIsXORed(t *testing.T) {
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{42}}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := CompileWith(program, CompileOpts{NoJunk: true})
|
|
|
|
rawPush := byte(OpPush) ^ dispatchKey
|
|
if code[0] != rawPush {
|
|
t.Errorf("expected bytecode[0]=0x%02X (XOR'd PUSH), got 0x%02X", rawPush, code[0])
|
|
}
|
|
|
|
rawHalt := byte(OpHalt) ^ dispatchKey
|
|
if code[9] != rawHalt {
|
|
t.Errorf("expected bytecode[9]=0x%02X (XOR'd HALT), got 0x%02X", rawHalt, code[9])
|
|
}
|
|
}
|
|
|
|
func TestJunkOpcodesCorrectness(t *testing.T) {
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{10}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{20}}},
|
|
{Instr: Instruction{Op: OpAdd}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
for i := 0; i < 50; i++ {
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
if result != 30 {
|
|
t.Fatalf("iteration %d: expected 30, got %d (bytecode len %d)", i, result, len(code))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOrOp(t *testing.T) {
|
|
// 0x0F OR 0xF0 = 0xFF
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{0x0F}}},
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{0xF0}}},
|
|
{Instr: Instruction{Op: OpOr}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 0xFF {
|
|
t.Errorf("expected 0xFF, got 0x%X", result)
|
|
}
|
|
}
|
|
|
|
func TestShrOp(t *testing.T) {
|
|
// 256 SHR 4 = 16
|
|
program := []LabelledInstr{
|
|
{Instr: Instruction{Op: OpPush, Args: []int64{256}}},
|
|
{Instr: Instruction{Op: OpShr, Args: []int64{4}}},
|
|
{Instr: Instruction{Op: OpHalt}},
|
|
}
|
|
|
|
code := Compile(program)
|
|
vm := NewVM(code, nil)
|
|
result := vm.Run()
|
|
|
|
if result != 16 {
|
|
t.Errorf("expected 16, got %d", result)
|
|
}
|
|
}
|