mirror of
https://github.com/lukaszraczylo/go-obfuscation.git
synced 2026-10-12 05:32:33 +00:00
249 lines
3.7 KiB
Go
249 lines
3.7 KiB
Go
package vm
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
const (
|
|
maxStackDepth = 1 << 16
|
|
stackMask = 0x5A5A5A5A5A5A5A5A
|
|
)
|
|
|
|
type VM struct {
|
|
stack []int64
|
|
sp int
|
|
vars map[int]int64
|
|
code []byte
|
|
pc int
|
|
halted bool
|
|
callouts map[int]func([]int64) int64
|
|
}
|
|
|
|
func NewVM(code []byte, callouts map[int]func([]int64) int64) *VM {
|
|
return &VM{
|
|
stack: make([]int64, maxStackDepth),
|
|
sp: 0,
|
|
vars: make(map[int]int64),
|
|
code: code,
|
|
pc: 0,
|
|
halted: false,
|
|
callouts: callouts,
|
|
}
|
|
}
|
|
|
|
func (vm *VM) push(v int64) {
|
|
if vm.sp >= maxStackDepth {
|
|
panic("vm: stack overflow")
|
|
}
|
|
vm.stack[vm.sp] = v ^ stackMask
|
|
vm.sp++
|
|
}
|
|
|
|
func (vm *VM) pop() int64 {
|
|
if vm.sp <= 0 {
|
|
panic("vm: stack underflow")
|
|
}
|
|
vm.sp--
|
|
return vm.stack[vm.sp] ^ stackMask
|
|
}
|
|
|
|
func (vm *VM) peek() int64 {
|
|
if vm.sp <= 0 {
|
|
panic("vm: stack empty on peek")
|
|
}
|
|
return vm.stack[vm.sp-1] ^ stackMask
|
|
}
|
|
|
|
func (vm *VM) Run() int64 {
|
|
for !vm.halted {
|
|
vm.Step()
|
|
}
|
|
if vm.sp == 0 {
|
|
return 0
|
|
}
|
|
return vm.peek()
|
|
}
|
|
|
|
func (vm *VM) Step() bool {
|
|
if vm.halted || vm.pc >= len(vm.code) {
|
|
vm.halted = true
|
|
return false
|
|
}
|
|
|
|
op, arg, size := DecodeInstr(vm.code, vm.pc)
|
|
vm.pc += size
|
|
|
|
switch op {
|
|
case OpNop:
|
|
// anti-analysis padding
|
|
|
|
case OpPush:
|
|
vm.push(arg)
|
|
|
|
case OpPop:
|
|
vm.pop()
|
|
|
|
case OpDup:
|
|
v := vm.pop()
|
|
vm.push(v)
|
|
vm.push(v)
|
|
|
|
case OpAdd:
|
|
b := vm.pop()
|
|
a := vm.pop()
|
|
vm.push(a + b)
|
|
|
|
case OpSub:
|
|
b := vm.pop()
|
|
a := vm.pop()
|
|
vm.push(a - b)
|
|
|
|
case OpMul:
|
|
b := vm.pop()
|
|
a := vm.pop()
|
|
vm.push(a * b)
|
|
|
|
case OpDiv:
|
|
b := vm.pop()
|
|
a := vm.pop()
|
|
if b == 0 {
|
|
panic("vm: division by zero")
|
|
}
|
|
vm.push(a / b)
|
|
|
|
case OpMod:
|
|
b := vm.pop()
|
|
a := vm.pop()
|
|
if b == 0 {
|
|
panic("vm: modulo by zero")
|
|
}
|
|
vm.push(a % b)
|
|
|
|
case OpXor:
|
|
b := vm.pop()
|
|
a := vm.pop()
|
|
vm.push(a ^ b)
|
|
|
|
case OpAnd:
|
|
b := vm.pop()
|
|
a := vm.pop()
|
|
vm.push(a & b)
|
|
|
|
case OpOr:
|
|
b := vm.pop()
|
|
a := vm.pop()
|
|
vm.push(a | b)
|
|
|
|
case OpNot:
|
|
a := vm.pop()
|
|
vm.push(^a)
|
|
|
|
case OpShl:
|
|
a := vm.pop()
|
|
vm.push(a << uint(arg))
|
|
|
|
case OpShr:
|
|
a := vm.pop()
|
|
vm.push(a >> uint(arg))
|
|
|
|
case OpCmpEq:
|
|
b := vm.pop()
|
|
a := vm.pop()
|
|
if a == b {
|
|
vm.push(1)
|
|
} else {
|
|
vm.push(0)
|
|
}
|
|
|
|
case OpCmpLt:
|
|
b := vm.pop()
|
|
a := vm.pop()
|
|
if a < b {
|
|
vm.push(1)
|
|
} else {
|
|
vm.push(0)
|
|
}
|
|
|
|
case OpCmpGt:
|
|
b := vm.pop()
|
|
a := vm.pop()
|
|
if a > b {
|
|
vm.push(1)
|
|
} else {
|
|
vm.push(0)
|
|
}
|
|
|
|
case OpJmp:
|
|
vm.pc = int(arg)
|
|
|
|
case OpJz:
|
|
v := vm.pop()
|
|
if v == 0 {
|
|
vm.pc = int(arg)
|
|
}
|
|
|
|
case OpJnz:
|
|
v := vm.pop()
|
|
if v != 0 {
|
|
vm.pc = int(arg)
|
|
}
|
|
|
|
case OpLoad:
|
|
vm.push(vm.vars[int(arg)])
|
|
|
|
case OpStore:
|
|
v := vm.pop()
|
|
vm.vars[int(arg)] = v
|
|
|
|
case OpCall:
|
|
fnID := int(arg)
|
|
if fn, ok := vm.callouts[fnID]; ok {
|
|
arity := int(vm.pop())
|
|
args := make([]int64, arity)
|
|
for i := arity - 1; i >= 0; i-- {
|
|
args[i] = vm.pop()
|
|
}
|
|
result := fn(args)
|
|
vm.push(result)
|
|
} else {
|
|
panic(fmt.Sprintf("vm: unknown callout ID %d", fnID))
|
|
}
|
|
|
|
case OpRet:
|
|
vm.halted = true
|
|
return false
|
|
|
|
case OpHalt:
|
|
vm.halted = true
|
|
return false
|
|
|
|
case OpSwap:
|
|
n := int(arg)
|
|
if n < 0 || vm.sp-n < 1 || vm.sp < 1 {
|
|
panic("vm: swap out of bounds")
|
|
}
|
|
topIdx := vm.sp - 1
|
|
nthIdx := vm.sp - 1 - n
|
|
vm.stack[topIdx], vm.stack[nthIdx] = vm.stack[nthIdx], vm.stack[topIdx]
|
|
|
|
case OpRot:
|
|
if vm.sp < 3 {
|
|
panic("vm: ROT requires 3 elements")
|
|
}
|
|
top := vm.stack[vm.sp-1]
|
|
vm.stack[vm.sp-1] = vm.stack[vm.sp-2]
|
|
vm.stack[vm.sp-2] = vm.stack[vm.sp-3]
|
|
vm.stack[vm.sp-3] = top
|
|
|
|
default:
|
|
// Junk opcodes and unknowns are silently consumed
|
|
// This makes disassembly analysis unreliable
|
|
}
|
|
|
|
return !vm.halted
|
|
}
|
|
|
|
func (vm *VM) PC() int { return vm.pc }
|
|
func (vm *VM) Halted() bool { return vm.halted }
|
|
func (vm *VM) SP() int { return vm.sp }
|