mirror of
https://github.com/lukaszraczylo/go-obfuscation.git
synced 2026-10-12 05:32:33 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
APP_NAME := demoapp
|
||||
SRC_DIR := cmd/demoapp
|
||||
BUILD_DIR := build
|
||||
OBFUSCATED := $(BUILD_DIR)/obfuscated
|
||||
BINARY := $(BUILD_DIR)/$(APP_NAME)
|
||||
BINARY_NORMAL := $(BUILD_DIR)/$(APP_NAME)-normal
|
||||
BINARY_GARBLE := $(BUILD_DIR)/$(APP_NAME)-garbled
|
||||
SEED := $(shell date +%s%N)
|
||||
|
||||
GARBLE := $(shell go env GOPATH)/bin/garble
|
||||
|
||||
.PHONY: all clean normal obfuscated compare transformer help run run-normal
|
||||
|
||||
help:
|
||||
@echo "Targets:"
|
||||
@echo " make obfuscated - Full pipeline: transform + garble + integrity"
|
||||
@echo " make normal - Build normal binary for comparison"
|
||||
@echo " make compare - Build both and compare"
|
||||
@echo " make transformer - Build only the transformer tool"
|
||||
@echo " make run - Run the obfuscated binary"
|
||||
@echo " make run-normal - Run the normal binary"
|
||||
@echo " make clean - Remove build artifacts"
|
||||
|
||||
all: obfuscated
|
||||
|
||||
transformer:
|
||||
@echo "=== Building transformer ==="
|
||||
go build -buildvcs=false -o $(BUILD_DIR)/transformer ./cmd/transformer
|
||||
|
||||
obfuscated: transformer
|
||||
@echo ""
|
||||
@echo "=== Stage 1: AST Transformation ==="
|
||||
@rm -rf $(OBFUSCATED)
|
||||
$(BUILD_DIR)/transformer -src $(SRC_DIR) -dst $(OBFUSCATED)/$(APP_NAME) -seed=$(SEED)
|
||||
@echo ""
|
||||
@echo "=== Stage 2: Garble compilation ==="
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
$(GARBLE) -literals -tiny build \
|
||||
-buildvcs=false \
|
||||
-trimpath \
|
||||
-ldflags="-s -w -buildid=" \
|
||||
-o $(BINARY_GARBLE) \
|
||||
./$(OBFUSCATED)/$(APP_NAME)
|
||||
@echo ""
|
||||
@echo "=== Stage 3: Compute integrity hash ==="
|
||||
@$$(go env GOPATH)/bin/garble version >/dev/null 2>&1 || true
|
||||
@HASH=$$(go run -buildvcs=false ./cmd/inthash $(BINARY_GARBLE) 2>/dev/null); \
|
||||
if [ -n "$$HASH" ]; then \
|
||||
echo "Integrity hash: $$HASH"; \
|
||||
else \
|
||||
echo "Note: integrity hash tool not built (run 'make inthash' first)"; \
|
||||
fi
|
||||
@echo ""
|
||||
@echo "=== Stage 4: Analysis ==="
|
||||
@echo "Size: $$(wc -c < $(BINARY_GARBLE) | tr -d ' ') bytes"
|
||||
@echo "Symbols: $$(nm $(BINARY_GARBLE) 2>/dev/null | wc -l | tr -d ' ')"
|
||||
@echo "Readable strings: $$(strings $(BINARY_GARBLE) | wc -l | tr -d ' ')"
|
||||
@echo ""
|
||||
@echo "=== Secret leakage scan ==="
|
||||
@for s in "sk-proj-FAKE" "super-secret" "P@ssw0rd" "jwt-signing" "LIC-XXXX" "postgres://"; do \
|
||||
count=$$(strings $(BINARY_GARBLE) | grep -c "$$s" || true); \
|
||||
if [ "$$count" != "0" ]; then \
|
||||
echo " LEAKED: $$s"; \
|
||||
else \
|
||||
echo " clean: $$s"; \
|
||||
fi; \
|
||||
done
|
||||
@echo ""
|
||||
@echo "=== Anti-analysis check ==="
|
||||
@echo "Function names visible:"
|
||||
@nm $(BINARY_GARBLE) 2>/dev/null | grep -c "main\." || echo " 0 (good)"
|
||||
@echo ""
|
||||
@echo "=== Done. Binary: $(BINARY_GARBLE) ==="
|
||||
|
||||
inthash:
|
||||
@echo "=== Building integrity hash tool ==="
|
||||
go build -buildvcs=false -o $(BUILD_DIR)/inthash ./cmd/inthash
|
||||
|
||||
normal:
|
||||
@echo "=== Building normal (unobfuscated) binary ==="
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
go build -buildvcs=false -o $(BINARY_NORMAL) ./$(SRC_DIR)
|
||||
@echo "Size: $$(wc -c < $(BINARY_NORMAL) | tr -d ' ') bytes"
|
||||
@echo "Symbols: $$(nm $(BINARY_NORMAL) 2>/dev/null | wc -l | tr -d ' ')"
|
||||
@echo "Readable strings: $$(strings $(BINARY_NORMAL) | wc -l | tr -d ' ')"
|
||||
@echo ""
|
||||
@echo "=== Secret leakage scan ==="
|
||||
@for s in "sk-proj-FAKE" "super-secret" "P@ssw0rd" "jwt-signing" "LIC-XXXX" "postgres://"; do \
|
||||
count=$$(strings $(BINARY_NORMAL) | grep -c "$$s" || true); \
|
||||
if [ "$$count" != "0" ]; then \
|
||||
echo " LEAKED: $$s"; \
|
||||
else \
|
||||
echo " clean: $$s"; \
|
||||
fi; \
|
||||
done
|
||||
|
||||
compare: normal obfuscated
|
||||
@echo ""
|
||||
@echo "========================================="
|
||||
@echo " COMPARISON SUMMARY"
|
||||
@echo "========================================="
|
||||
@echo " Normal Obfuscated"
|
||||
@echo "Size: $$(wc -c < $(BINARY_NORMAL) | tr -d ' ') $$(wc -c < $(BINARY_GARBLE) | tr -d ' ')"
|
||||
@echo "Symbols: $$(nm $(BINARY_NORMAL) 2>/dev/null | wc -l | tr -d ' ') $$(nm $(BINARY_GARBLE) 2>/dev/null | wc -l | tr -d ' ')"
|
||||
@echo "Strings: $$(strings $(BINARY_NORMAL) | wc -l | tr -d ' ') $$(strings $(BINARY_GARBLE) | wc -l | tr -d ' ')"
|
||||
@echo "========================================="
|
||||
|
||||
run: obfuscated
|
||||
@$(BINARY_GARBLE)
|
||||
|
||||
run-normal: normal
|
||||
@$(BINARY_NORMAL)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
@@ -0,0 +1,407 @@
|
||||
# Go Binary Obfuscation Toolkit
|
||||
|
||||
A multi-layer Go binary obfuscation pipeline that combines source-code transformation, compiler-level obfuscation, and runtime protection to make reverse engineering prohibitively expensive.
|
||||
|
||||
## Overview
|
||||
|
||||
The toolkit operates as a staged pipeline:
|
||||
|
||||
```
|
||||
Source Code
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Stage 1: Source Transformer │ Text-based source rewriting
|
||||
│ (cmd/transformer) │ 9 obfuscation passes
|
||||
└─────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Stage 2: Garble Compilation │ garble -literals -tiny
|
||||
│ (mvdan.cc/garble) │ -trimpath -ldflags="-s -w"
|
||||
└─────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Stage 3: Integrity Hash │ SHA-256 of .text / __text
|
||||
│ (cmd/inthash) │ section for tamper detection
|
||||
└─────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Stage 4: Analysis │ Size, symbols, strings,
|
||||
│ (Makefile) │ secret leakage scan
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Prerequisites
|
||||
go install mvdan.cc/garble@latest
|
||||
|
||||
# Build and compare normal vs obfuscated
|
||||
make compare
|
||||
|
||||
# Run the obfuscated binary
|
||||
make run
|
||||
|
||||
# Run just the transformer
|
||||
make transformer
|
||||
```
|
||||
|
||||
### `make compare` Output
|
||||
|
||||
```
|
||||
Normal Obfuscated
|
||||
Size: 2707026 3063458
|
||||
Symbols: 2864 58
|
||||
Strings: 15335 4302
|
||||
Secret leaks: 1 (LIC-*) 0
|
||||
Function names: visible 0 (good)
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
obfuscator/
|
||||
├── cmd/
|
||||
│ ├── transformer/ CLI for source transformation
|
||||
│ ├── demoapp/ Target application with embedded secrets
|
||||
│ └── inthash/ Integrity hash computation tool
|
||||
├── internal/
|
||||
│ └── transform/ Core transformation engine (9 passes)
|
||||
├── pkg/
|
||||
│ ├── strenc/ AES-256-GCM encrypted string pool
|
||||
│ ├── stringcrypt/ XOR string encryption + random identifiers
|
||||
│ ├── vm/ Stack-based bytecode virtual machine
|
||||
│ ├── syscallobf/ Obfuscated syscall dispatch
|
||||
│ ├── integrity/ Binary .text section integrity verification
|
||||
│ ├── antidebug/ Anti-debugging / anti-hooking checks
|
||||
│ └── pageman/ Encrypted memory buffer management
|
||||
└── Makefile Full build pipeline
|
||||
```
|
||||
|
||||
## Obfuscation Layers
|
||||
|
||||
### 1. String Encryption (`pkg/strenc`)
|
||||
|
||||
All string literals are replaced with `_decStr(index)` calls backed by an AES-256-GCM encrypted pool.
|
||||
|
||||
**How it works:**
|
||||
- The transformer scans Go AST for string literals (skipping imports and directives)
|
||||
- Each plaintext is length-padded to 64-byte blocks with a 2-byte little-endian length prefix
|
||||
- A random 32-byte master seed is generated per build
|
||||
- Per-string IVs are generated; subkeys are derived via HMAC-SHA256(masterKey, IV)
|
||||
- Encryption uses AES-256-GCM with the derived subkey
|
||||
- The generated code includes `_masterSeed`, `_encEntries`, `_encPool`, and a `//go:noinline` `_decStr()` function
|
||||
- At runtime, `GetString(index)` decrypts on demand; the plaintext is never present as a static string
|
||||
|
||||
**Key derivation (`pkg/strenc/keyderive.go`):**
|
||||
```
|
||||
seed ──► HMAC-SHA256(seed, "strenc.masterkey.v1") ──► first 16 bytes
|
||||
──► HMAC-SHA256(seed, "strenc.masterkey.v2") ──► last 16 bytes
|
||||
──► 32-byte master key
|
||||
```
|
||||
|
||||
### 2. Opaque Predicates
|
||||
|
||||
Injects always-true conditional branches into function bodies at ~40% probability (after 3+ statements).
|
||||
|
||||
**6 always-true expression variants:**
|
||||
- `(a*7) % 7 == 0`
|
||||
- `(a ^ a) == 0`
|
||||
- `^(a | a) == ^a`
|
||||
- `(a & a) == a`
|
||||
- `len([N]byte{}) == 0`
|
||||
- `(b + a) > (b + a - 1)`
|
||||
|
||||
These are syntactically valid Go expressions that always evaluate to `true` but require symbolic execution or constant folding to prove.
|
||||
|
||||
### 3. Dead Code Injection
|
||||
|
||||
Injects always-false conditional branches containing unreachable code at ~30% probability.
|
||||
|
||||
**6 always-false expression variants:**
|
||||
- `(a - a) < 0`
|
||||
- `func() bool { return 1 < 0 }()`
|
||||
- `func() bool { var _x = a; return _x > _x }()`
|
||||
- `func() bool { var _x int = a; _x -= a; return _x > 0 }()`
|
||||
- `len([N]byte{}) < 0`
|
||||
- `func() bool { return (a^a) > 0 && (a^a) < 0 }()`
|
||||
|
||||
### 4. Bogus Control Flow
|
||||
|
||||
Injects computationally expensive but semantically dead branches at ~25% probability.
|
||||
|
||||
**4 bogus branch variants:**
|
||||
- Modulo arithmetic loop: `v := int64(1); for i := 0; i < 10; i++ { v = v*7%49999999 }; return v == 1`
|
||||
- Squared even check: `v := a*a; return v%2 == 0 && v > 0`
|
||||
- Byte array length: `v := [4]byte{0x72, 0x6F, 0x6F, 0x74}; return len(v) == 4`
|
||||
- Summation loop: `v := func() int { n := 0; for i := 1; i <= 100; i++ { n += i }; return n }()`
|
||||
|
||||
### 5. Indirect Dispatch
|
||||
|
||||
Renames unexported functions and creates thin wrappers to obscure call targets.
|
||||
|
||||
```go
|
||||
// Before:
|
||||
func generateToken(userID string) string { ... }
|
||||
|
||||
// After:
|
||||
func generateToken_obf4721(userID string) string { ... } // renamed
|
||||
func generateToken(userID string) string { // wrapper
|
||||
return generateToken_obf4721(userID)
|
||||
}
|
||||
```
|
||||
|
||||
Skips `main`, `init`, exported functions, and `_`-prefixed functions.
|
||||
|
||||
### 6. Control-Flow Flattening
|
||||
|
||||
Converts function bodies into a state-machine dispatcher pattern.
|
||||
|
||||
```go
|
||||
// Before: // After:
|
||||
func foo() { func foo() {
|
||||
a := 1 _stX := 0
|
||||
b := a + 2 for {
|
||||
c := b * 3 switch _stX {
|
||||
return c case 0: a = 1; _stX = 1
|
||||
} case 1: b = a + 2; _stX = 2
|
||||
case 2: c = b * 3; _stX = 3
|
||||
case 3: return c
|
||||
default: return
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Safety checks:**
|
||||
- Only applies to functions with 5+ body lines
|
||||
- Excludes `main` and `init` functions
|
||||
- Pre-scan validates all `:=` assignments have inferrable types before hoisting
|
||||
- Validates variable names are valid Go identifiers (avoids misinterpreting injected opaque predicates)
|
||||
- Multi-var declarations (`a, b := ...`) are handled separately
|
||||
|
||||
### 7. Code Splicing
|
||||
|
||||
Similar to flattening but targets functions with `return` statements, splitting them into 2-3 fragments joined by a state machine.
|
||||
|
||||
### 8. Build ID Polymorphism
|
||||
|
||||
Injects a unique build identifier derived from the build seed:
|
||||
|
||||
```go
|
||||
var _buildID = "BUILD_1780185895842347000_A3F2B1C9" // polymorphic build marker
|
||||
var _ = _buildID
|
||||
```
|
||||
|
||||
Every build with a different seed produces a structurally different binary, defeating signature-based detection.
|
||||
|
||||
### 9. Function Virtualization (`pkg/vm`)
|
||||
|
||||
Replaces simple functions with a custom stack-based bytecode VM.
|
||||
|
||||
**Supported patterns:**
|
||||
- `len(x) > N`, `len(x) == N`, `len(x) < N`, `len(x) >= N`
|
||||
- Two-param arithmetic: `a+b`, `a-b`, `a*b`, `a/b`, `a%b`, `a^b`, `a&b`, `a|b`
|
||||
- Single-param arithmetic: `a+N`, `a-N`, `a*N`, etc.
|
||||
|
||||
**VM design (`pkg/vm/`):**
|
||||
- 28 opcodes (0x00-0x1B) + 4 junk opcodes (0x1C-0x1F)
|
||||
- Stack values XOR-masked with `0x5A5A5A5A5A5A5A5A` to resist memory analysis
|
||||
- Opcode dispatch key: `0xA5` — all opcodes XOR'd before encoding
|
||||
- Variable-length instruction encoding (1-9 bytes per instruction)
|
||||
- External callout mechanism: `PUSH args..., PUSH arity, CALL funcID`
|
||||
- Unknown opcodes silently consumed (breaks linear disassembly)
|
||||
|
||||
**Opcodes:**
|
||||
|
||||
| Opcode | Hex | Arg | Description |
|
||||
|--------|------|-------|-------------|
|
||||
| NOP | 0x00 | none | Anti-analysis padding |
|
||||
| PUSH | 0x01 | i64 | Push immediate |
|
||||
| POP | 0x02 | none | Discard top |
|
||||
| DUP | 0x03 | none | Duplicate top |
|
||||
| ADD | 0x04 | none | a + b |
|
||||
| SUB | 0x05 | none | a - b |
|
||||
| MUL | 0x06 | none | a * b |
|
||||
| DIV | 0x07 | none | a / b |
|
||||
| MOD | 0x08 | none | a % b |
|
||||
| XOR | 0x09 | none | a ^ b |
|
||||
| AND | 0x0A | none | a & b |
|
||||
| OR | 0x0B | none | a \| b |
|
||||
| NOT | 0x0C | none | ~a |
|
||||
| SHL | 0x0D | i8 | a << n |
|
||||
| SHR | 0x0E | i8 | a >> n |
|
||||
| CMP_EQ | 0x0F | none | a == b ? 1 : 0 |
|
||||
| CMP_LT | 0x10 | none | a < b ? 1 : 0 |
|
||||
| CMP_GT | 0x11 | none | a > b ? 1 : 0 |
|
||||
| JMP | 0x12 | i32 | Unconditional jump |
|
||||
| JZ | 0x13 | i32 | Jump if zero |
|
||||
| JNZ | 0x14 | i32 | Jump if not zero |
|
||||
| LOAD | 0x15 | i16 | Load variable |
|
||||
| STORE | 0x16 | i16 | Store variable |
|
||||
| CALL | 0x17 | i16 | Call external function |
|
||||
| RET | 0x18 | none | Return |
|
||||
| HALT | 0x19 | none | Stop execution |
|
||||
| SWAP | 0x1A | i8 | Swap stack positions |
|
||||
| ROT | 0x1B | none | Rotate top 3 |
|
||||
|
||||
## Runtime Protection Packages
|
||||
|
||||
### Syscall Obfuscation (`pkg/syscallobf`)
|
||||
|
||||
Hides syscall numbers from static analysis using XOR-encoded dispatch.
|
||||
|
||||
- **Encoding:** Each syscall number is XOR'd with a random `uint64` dispatch key generated once via `sync.Once`
|
||||
- **Linux (`syscall_linux.go`):** Wraps `ptrace` (101), `getpid` (39), `kill` (62), `open` (2), `close` (3), `read` (0), `lseek` (8), `mmap` (9), `mprotect` (10) via `syscall.RawSyscall`
|
||||
- **Darwin (`syscall_darwin.go`):** Returns `ENOSYS` stubs (platform-specific implementation pending)
|
||||
|
||||
The anti-debugging package uses these for stealthy `/proc/self/mem` and `/proc/self/maps` access.
|
||||
|
||||
### Anti-Debugging (`pkg/antidebug`)
|
||||
|
||||
**`Check()` runs all detection methods:**
|
||||
|
||||
| Check | Platform | Description |
|
||||
|-------|----------|-------------|
|
||||
| `checkPtrace()` | Linux | Reads `/proc/self/status` for non-zero `TracerPid` |
|
||||
| `disableCoreDump()` | Linux | Writes "0" to `/proc/self/coredump_filter` |
|
||||
| `checkTiming()` | All | 1M loop iterations must complete in <200ms (detects single-stepping) |
|
||||
| `checkEnv()` | All | Checks for `GODEBUG`, `DELVE_LISTENER`, `DEBUGINFOD_URL`, `RR_LOG_FILE`, `_JAVA_OPTIONS`, `LD_PRELOAD`, `DYLD_INSERT_LIBRARIES` |
|
||||
| `checkParentProcess()` | Linux | Reads `/proc/<ppid>/comm`, matches against 12 debugger names |
|
||||
| `checkFunctionPrologues()` | Linux | Reads `/proc/self/mem` at function addresses, checks for JMP hooks (0xE9, 0xEB, 0xFF 0x25) and INT3 breakpoints (0xCC) |
|
||||
| `scanForBreakpoints()` | Linux | Parses `/proc/self/maps`, reads executable regions, flags ≥8 consecutive 0xCC bytes |
|
||||
|
||||
**Background goroutines:**
|
||||
- `AntiAttach()` — polls `checkPtrace()` every 500ms, exits with code 66 on detection
|
||||
- `AntiDump()` — re-disables core dumps every 2s
|
||||
|
||||
All `/proc` reads use `syscallobf` raw syscalls to avoid `os.Open` hooks.
|
||||
|
||||
### Binary Integrity (`pkg/integrity`)
|
||||
|
||||
Verifies the binary hasn't been tampered with at runtime.
|
||||
|
||||
- **Linux:** SHA-256 of the `.text` ELF section (via `debug/elf`)
|
||||
- **macOS:** SHA-256 of the `__text` Mach-O section (via `debug/macho`)
|
||||
- **Fallback:** SHA-256 of the entire file
|
||||
|
||||
**Usage:**
|
||||
```go
|
||||
// After building, compute hash:
|
||||
// go run ./cmd/inthash ./mybinary
|
||||
|
||||
integrity.SetExpectedHashHex("03b1b8341708322379195b1d4427fb15...")
|
||||
integrity.StartMonitor(30*time.Second, func(err error) {
|
||||
// tamper detected
|
||||
})
|
||||
```
|
||||
|
||||
`StartMonitor` spawns a goroutine that verifies the hash at the given interval (with random initial jitter). Exits with code 66 on mismatch.
|
||||
|
||||
### Encrypted Memory (`pkg/pageman`)
|
||||
|
||||
`SecureBuffer` provides XOR-encrypted memory with automatic locking.
|
||||
|
||||
**Features:**
|
||||
- Random 32-byte key per buffer
|
||||
- XOR encryption with per-byte key rotation (`rotateKey(b, pos) = b<<shift | b>>(8-shift)` where `shift = (pos*3+7)%8`)
|
||||
- `Access(fn)` — auto-unlock, execute callback with plaintext, auto-relock
|
||||
- `Close()` — zeros all data and key material
|
||||
- Thread-safe via `sync.Mutex`
|
||||
|
||||
**Guard goroutine (`guard.go`):**
|
||||
- Watches multiple `SecureBuffer`s
|
||||
- `enforceAutoLock()` — re-locks buffers that exceeded their `autoLock` duration
|
||||
- `checkMapsTampering()` — compares `/proc/self/maps` snapshots; force-locks all buffers if the memory map changed (detects debugger attachment / memory mapping modifications)
|
||||
|
||||
## Transformer CLI
|
||||
|
||||
```
|
||||
Usage: transformer -src <dir> -dst <dir> [flags]
|
||||
|
||||
Flags:
|
||||
-src string Source directory to transform
|
||||
-dst string Destination directory for transformed source
|
||||
-strings Encrypt string literals (default true)
|
||||
-opaque Inject opaque predicates (default true)
|
||||
-deadcode Inject dead code (default true)
|
||||
-randomize Randomize declaration order (default true)
|
||||
-indirect Indirect function dispatch (default true)
|
||||
-bogus-cfg Bogus control-flow injection (default true)
|
||||
-flatten Control-flow flattening (default true)
|
||||
-vm Virtualize simple functions into VM bytecode (default true)
|
||||
-buildid Inject unique build ID constant (default true)
|
||||
-seed int Build seed for polymorphic output (0 = use current timestamp)
|
||||
```
|
||||
|
||||
## Build Pipeline
|
||||
|
||||
The `Makefile` orchestrates the full pipeline:
|
||||
|
||||
```makefile
|
||||
# Stage 1: Source transformation
|
||||
transformer -src cmd/demoapp -dst build/obfuscated/demoapp -seed=$(SEED)
|
||||
|
||||
# Stage 2: Garble compilation
|
||||
garble -literals -tiny build -buildvcs=false -trimpath \
|
||||
-ldflags="-s -w -buildid=" -o build/demoapp-garbled ./build/obfuscated/demoapp
|
||||
|
||||
# Stage 3: Integrity hash
|
||||
go run ./cmd/inthash build/demoapp-garbled
|
||||
|
||||
# Stage 4: Analysis
|
||||
# - Binary size
|
||||
# - Symbol count (nm)
|
||||
# - String count (strings)
|
||||
# - Secret leakage scan
|
||||
# - Function name visibility
|
||||
```
|
||||
|
||||
## Demo Application
|
||||
|
||||
`cmd/demoapp/main.go` contains intentionally leaked secrets for testing:
|
||||
|
||||
```go
|
||||
const (
|
||||
apiKey = "sk-proj-FAKE-KEY-1234567890abcdef"
|
||||
secretVal = "super-secret-license-key-DO-NOT-SHARE"
|
||||
dbConnStr = "postgres://admin:P@[email protected]:5432/production"
|
||||
jwtSecret = "my-jwt-signing-secret-2024"
|
||||
)
|
||||
var licenseKey = "LIC-XXXX-YYYY-ZZZZ-PRODUCTION"
|
||||
```
|
||||
|
||||
The `make compare` target verifies all of these are hidden in the obfuscated binary.
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Run all package tests
|
||||
go test ./pkg/...
|
||||
|
||||
# VM tests (22 tests: arithmetic, bitwise, comparisons, jumps, callouts, determinism)
|
||||
go test ./pkg/vm/ -v
|
||||
|
||||
# Syscall obfuscation tests
|
||||
go test ./pkg/syscallobf/ -v
|
||||
|
||||
# Vet all source
|
||||
go vet ./...
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Go 1.26.3+ (darwin/arm64 or linux/amd64)
|
||||
- `garble` (`go install mvdan.cc/garble@latest`)
|
||||
- No external Go dependencies (stdlib only)
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Text-based transformation:** The transformer operates on source text, not Go AST. This limits what can be reliably transformed (e.g., type inference for variable hoisting is heuristic).
|
||||
- **Darwin syscall stubs:** `pkg/syscallobf` returns `ENOSYS` on macOS. Linux is the primary target for runtime protections.
|
||||
- **Garble required:** Stage 2 depends on `mvdan.cc/garble` for name mangling, literal encryption, and binary stripping.
|
||||
- **Simple function virtualization only:** The VM currently handles len-comparisons and arithmetic. Complex control flow is not virtualized.
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,121 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/self-evolving-research/obfuscator/pkg/vm"
|
||||
"github.com/self-evolving-research/obfuscator/pkg/strenc"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _buildID = "BUILD_1780185895842347000_5717911B" // polymorphic build marker
|
||||
var _ = _buildID
|
||||
|
||||
func getVersion() string { return getVersion_obf2511() }
|
||||
func getBuildID() string { return getBuildID_obf7656() }
|
||||
func processRequest(userID string) { processRequest_obf1301(userID) }
|
||||
func generateToken(userID string) string { return generateToken_obf5646(userID) }
|
||||
func validateLicense(key string) bool { return validateLicense_obf9184(key) }
|
||||
func maskString(s string) string { return maskString_obf9182(s) }
|
||||
var _masterSeed = [32]byte{0xF0, 0x9C, 0xC8, 0x1C, 0xD9, 0x25, 0xA4, 0x69, 0xFE, 0xED, 0xA1, 0x92, 0xD6, 0x1A, 0x62, 0xF1, 0x0C, 0xA7, 0xD0, 0xB9, 0xC3, 0x3B, 0xB4, 0xE0, 0x41, 0x5F, 0x79, 0x14, 0x1F, 0xBB, 0xE1, 0xC5}
|
||||
var _encEntries = []strenc.Entry{
|
||||
{Ciphertext: []byte{0x97, 0xFC, 0x87, 0x6E, 0x07, 0xCF, 0xED, 0xBC, 0xA2, 0xCF, 0x6D, 0x5F, 0xE9, 0x9C, 0x51, 0x81, 0xEA, 0x72, 0x61, 0x41, 0xE0, 0x06, 0xC1, 0x62, 0x99, 0xFE, 0xB7, 0x72, 0xA0, 0x7C, 0x7D, 0xEC, 0x74, 0xF7, 0xA1, 0x88, 0xFE, 0x91, 0x69, 0xE1, 0x6A, 0xA4, 0x71, 0x73, 0xAB, 0x88, 0xBB, 0x91, 0xE5, 0x6E, 0x74, 0xBC, 0x56, 0xD5, 0x16, 0x88, 0x84, 0x96, 0x53, 0x56, 0xCA, 0x2D, 0x98, 0x1D, 0xB7, 0x3B, 0x15, 0xD3, 0xF7, 0x39, 0xCC, 0x73, 0x35, 0xE5, 0x72, 0x5F, 0x4E, 0x72, 0xB4, 0x51, 0x81, 0xA9, 0x9E, 0x5D, 0x1A, 0xF4, 0x53, 0x86, 0x81, 0x1C, 0x20, 0x00}, IV: [16]byte{0x97, 0xFC, 0x87, 0x6E, 0x07, 0xCF, 0xED, 0xBC, 0xA2, 0xCF, 0x6D, 0x5F, 0xF8, 0x65, 0x80, 0x84}},
|
||||
{Ciphertext: []byte{0x67, 0xDC, 0x11, 0xFA, 0xDC, 0x10, 0x70, 0xA5, 0x8B, 0x78, 0xB7, 0x0B, 0xB2, 0x90, 0xB2, 0x38, 0xEB, 0x1D, 0xFD, 0x3F, 0x25, 0xA9, 0xE7, 0x28, 0x8A, 0x58, 0x82, 0x98, 0xDF, 0xB3, 0xE0, 0x65, 0x82, 0x22, 0x84, 0x91, 0x0E, 0xE0, 0x71, 0xF5, 0x6A, 0x7B, 0x2D, 0xEC, 0x8B, 0x67, 0x34, 0xB2, 0x71, 0xEE, 0x0C, 0xC4, 0xA2, 0x4E, 0x6C, 0x8C, 0x81, 0xCB, 0x41, 0x88, 0x90, 0x28, 0xDA, 0x78, 0x70, 0x45, 0x7A, 0xBB, 0xBD, 0xAD, 0x38, 0x06, 0x15, 0xCA, 0xF4, 0xA7, 0x36, 0xE8, 0xA9, 0x88, 0x8E, 0xFF, 0x72, 0x27, 0x96, 0x87, 0x92, 0xD1, 0x81, 0x0C, 0x7C, 0x80}, IV: [16]byte{0x67, 0xDC, 0x11, 0xFA, 0xDC, 0x10, 0x70, 0xA5, 0x8B, 0x78, 0xB7, 0x0B, 0x1F, 0xC3, 0xF1, 0xEA}},
|
||||
{Ciphertext: []byte{0x80, 0xA2, 0x11, 0x9A, 0xE8, 0xEB, 0x2C, 0x1D, 0x2B, 0xBE, 0x8D, 0x1E, 0xD9, 0x87, 0x84, 0x25, 0xCD, 0xF5, 0x8E, 0xF7, 0xA2, 0xF2, 0x71, 0x01, 0x8A, 0xCC, 0xF3, 0x86, 0xC7, 0x93, 0x1B, 0xF6, 0xAC, 0xF0, 0x45, 0xA4, 0x62, 0x9F, 0xF7, 0x2B, 0x1C, 0x24, 0x4F, 0x82, 0xCA, 0x08, 0xC3, 0x9C, 0xB0, 0x2E, 0xFA, 0xAE, 0xF6, 0x27, 0x71, 0xDB, 0xE2, 0xC9, 0x50, 0x6E, 0x20, 0xA9, 0x39, 0x8A, 0x2D, 0x2C, 0x12, 0x24, 0x2C, 0x57, 0x16, 0x0A, 0x52, 0xB6, 0x4D, 0x12, 0xA0, 0x4F, 0x7F, 0x5D, 0x0F, 0xF0, 0xB0, 0x41, 0x5C, 0x57, 0x46, 0x4C, 0x7D, 0xAC, 0xB1, 0x00}, IV: [16]byte{0x80, 0xA2, 0x11, 0x9A, 0xE8, 0xEB, 0x2C, 0x1D, 0x2B, 0xBE, 0x8D, 0x1E, 0x82, 0xA5, 0xF5, 0x72}},
|
||||
{Ciphertext: []byte{0xAE, 0x47, 0xCD, 0xAD, 0x46, 0x2C, 0xD7, 0xC2, 0xF4, 0x37, 0x25, 0xC9, 0x14, 0x8E, 0x90, 0xBD, 0x63, 0xA8, 0x0C, 0xA5, 0x86, 0x02, 0xE0, 0x9C, 0xCD, 0x70, 0xFC, 0x20, 0x51, 0x01, 0x0E, 0xCF, 0x7D, 0xD2, 0xB4, 0x6B, 0x07, 0xE9, 0x2A, 0x6E, 0x89, 0x2F, 0xAC, 0x7A, 0x07, 0xCA, 0x0B, 0x50, 0x12, 0x8F, 0x69, 0x29, 0xC4, 0x58, 0xB0, 0x0E, 0x6E, 0xBF, 0x0C, 0xBD, 0x9B, 0x8B, 0xC0, 0x10, 0xB8, 0x7F, 0x89, 0x7F, 0x85, 0xF8, 0x04, 0xFF, 0x24, 0xE8, 0x68, 0x12, 0x6D, 0xBC, 0x2E, 0xF3, 0xD0, 0xD5, 0x4A, 0x38, 0x1C, 0x12, 0x28, 0xD5, 0x1D, 0x55, 0xC8, 0xF6}, IV: [16]byte{0xAE, 0x47, 0xCD, 0xAD, 0x46, 0x2C, 0xD7, 0xC2, 0xF4, 0x37, 0x25, 0xC9, 0xE9, 0x58, 0x3C, 0x30}},
|
||||
{Ciphertext: []byte{0x46, 0x59, 0xD5, 0x11, 0x98, 0xE7, 0xC9, 0x0F, 0x00, 0xB9, 0x73, 0xD7, 0x62, 0xF0, 0x2E, 0xC6, 0x19, 0xF9, 0x45, 0xF7, 0x0B, 0x57, 0x35, 0x1F, 0xC9, 0xEB, 0x80, 0xBE, 0x19, 0xBE, 0x6B, 0xE3, 0x82, 0x39, 0x88, 0xF6, 0x95, 0x67, 0x47, 0x7D, 0xBC, 0xC9, 0xD3, 0x83, 0x1C, 0xAE, 0x47, 0xEF, 0xD6, 0x6D, 0x49, 0x01, 0xEA, 0x7F, 0x9A, 0x96, 0xEC, 0xA8, 0x22, 0x7A, 0x1D, 0x49, 0x2B, 0x34, 0x22, 0x2A, 0xFF, 0x3F, 0xF1, 0xEB, 0xDD, 0x5B, 0xB5, 0xFD, 0xB3, 0x6B, 0x4A, 0x44, 0x16, 0xCB, 0x9A, 0x70, 0x1F, 0xD9, 0x32, 0x0C, 0x4D, 0x96, 0x6D, 0x26, 0xCB, 0x93}, IV: [16]byte{0x46, 0x59, 0xD5, 0x11, 0x98, 0xE7, 0xC9, 0x0F, 0x00, 0xB9, 0x73, 0xD7, 0x67, 0x7C, 0x66, 0x1A}},
|
||||
{Ciphertext: []byte{0xB5, 0x89, 0x78, 0x8E, 0x5F, 0x5D, 0xBB, 0xF4, 0xF2, 0x5E, 0x18, 0x2A, 0xA8, 0x1A, 0x69, 0x70, 0xF9, 0xB9, 0x8A, 0xDF, 0x4D, 0x25, 0x6A, 0x78, 0xD8, 0xD4, 0x16, 0xD8, 0xDF, 0x39, 0x2B, 0x78, 0x51, 0x8C, 0xBA, 0x8D, 0x76, 0xBF, 0x53, 0xCB, 0xDB, 0x59, 0xEF, 0xDF, 0x9C, 0x1C, 0xDA, 0xC3, 0x3A, 0x89, 0xD8, 0x31, 0x7E, 0xD3, 0x79, 0x08, 0x66, 0xF5, 0x7F, 0x58, 0x2B, 0x7D, 0xA9, 0x83, 0xE3, 0xBC, 0x64, 0xC7, 0xD9, 0xCC, 0x19, 0xAD, 0xF2, 0x93, 0x3C, 0xB1, 0x61, 0xFC, 0x7B, 0x03, 0x30, 0x51, 0x41, 0x48, 0x51, 0x04, 0x45, 0x14, 0xD1, 0x14, 0x72, 0x0B}, IV: [16]byte{0xB5, 0x89, 0x78, 0x8E, 0x5F, 0x5D, 0xBB, 0xF4, 0xF2, 0x5E, 0x18, 0x2A, 0x68, 0x6E, 0x1A, 0x33}},
|
||||
{Ciphertext: []byte{0x8B, 0x4E, 0xB4, 0x83, 0x28, 0x49, 0x0F, 0x3B, 0x26, 0xB3, 0x86, 0x31, 0x7F, 0x88, 0x6C, 0x57, 0x53, 0x5B, 0x22, 0xB7, 0x9D, 0xBB, 0x9B, 0x16, 0x9C, 0xAC, 0xAE, 0xC2, 0xAA, 0x7E, 0x59, 0xC5, 0x6C, 0x57, 0xB2, 0xA3, 0x0B, 0xDE, 0x15, 0x42, 0x3A, 0x8C, 0x04, 0x31, 0x4F, 0x44, 0x7F, 0x5E, 0x9B, 0x41, 0x8A, 0x00, 0x05, 0x44, 0x0A, 0x7C, 0x87, 0xD7, 0xF3, 0x56, 0x03, 0xFF, 0x71, 0x58, 0xC2, 0x7E, 0xB0, 0xC9, 0x57, 0x00, 0x78, 0x4A, 0xF2, 0x3C, 0xAB, 0xB0, 0x30, 0x65, 0x1C, 0xB7, 0xD5, 0xC6, 0xC3, 0x16, 0x01, 0x86, 0xC9, 0xD2, 0x8D, 0x04, 0xF0, 0xB9}, IV: [16]byte{0x8B, 0x4E, 0xB4, 0x83, 0x28, 0x49, 0x0F, 0x3B, 0x26, 0xB3, 0x86, 0x31, 0x83, 0xD0, 0x78, 0x5B}},
|
||||
{Ciphertext: []byte{0x25, 0xB7, 0xFA, 0x94, 0x6A, 0x73, 0x1F, 0x9E, 0xE2, 0x13, 0x6E, 0x18, 0x07, 0x94, 0x74, 0x6F, 0xE6, 0xB9, 0x48, 0xCD, 0xFA, 0x6B, 0x7D, 0xB4, 0x84, 0x1B, 0xDC, 0x3C, 0x30, 0x4D, 0x99, 0xA4, 0xA7, 0x9A, 0x52, 0x43, 0x41, 0x7B, 0x9F, 0x86, 0x7A, 0x7C, 0x25, 0x55, 0x75, 0x38, 0x4B, 0x37, 0x92, 0x97, 0x96, 0xE5, 0x35, 0xDD, 0x44, 0xCF, 0x45, 0xF1, 0x9F, 0x92, 0x9B, 0xEB, 0x30, 0x9F, 0x7E, 0x64, 0x98, 0x5D, 0x78, 0x31, 0xFA, 0x73, 0x57, 0x1D, 0xF0, 0xAC, 0xBB, 0xA1, 0xC9, 0x57, 0x4B, 0x8D, 0x62, 0x80, 0xBE, 0x1C, 0xA9, 0xE1, 0x34, 0x56, 0xB3, 0xAA}, IV: [16]byte{0x25, 0xB7, 0xFA, 0x94, 0x6A, 0x73, 0x1F, 0x9E, 0xE2, 0x13, 0x6E, 0x18, 0x75, 0xB7, 0x15, 0x4C}},
|
||||
{Ciphertext: []byte{0x10, 0xD7, 0x54, 0xA5, 0x47, 0xDC, 0x58, 0xA2, 0xCD, 0x56, 0x39, 0x85, 0x5E, 0x43, 0x93, 0x14, 0x64, 0xF7, 0x22, 0x60, 0x4A, 0x38, 0x35, 0xF8, 0x6C, 0xA5, 0xE1, 0x3C, 0xD0, 0x42, 0xD8, 0x47, 0xBF, 0xE2, 0x81, 0x35, 0x4D, 0x56, 0xCC, 0x94, 0xA2, 0xF1, 0xD7, 0x5F, 0xD7, 0x48, 0xA5, 0x76, 0x12, 0x9D, 0x42, 0xAC, 0xA6, 0xDC, 0x3D, 0x16, 0xEC, 0x84, 0xC6, 0x1C, 0x00, 0x3C, 0xDA, 0xE1, 0x41, 0xD2, 0x22, 0x30, 0xE1, 0x01, 0x08, 0x71, 0x9A, 0xDF, 0x5B, 0x18, 0xC1, 0x70, 0xA9, 0x05, 0xAF, 0xAA, 0x02, 0x96, 0xC7, 0x73, 0x1F, 0xD3, 0x26, 0x9D, 0xCC, 0xA1}, IV: [16]byte{0x10, 0xD7, 0x54, 0xA5, 0x47, 0xDC, 0x58, 0xA2, 0xCD, 0x56, 0x39, 0x85, 0x53, 0x5F, 0xA9, 0x9E}},
|
||||
{Ciphertext: []byte{0x41, 0xE3, 0xEF, 0x63, 0x8F, 0xE4, 0xD6, 0x20, 0xE1, 0x94, 0x93, 0x56, 0x6D, 0xA1, 0x9F, 0x44, 0xC2, 0x6E, 0x64, 0xAE, 0xB6, 0x9C, 0x95, 0x8E, 0x0F, 0x08, 0xF6, 0xAF, 0xDF, 0x68, 0xFE, 0x20, 0xC3, 0x6D, 0xF5, 0x03, 0x07, 0xCD, 0xA2, 0x6B, 0xD1, 0x5D, 0xBA, 0x63, 0xAB, 0x29, 0x7C, 0x79, 0xD1, 0xC8, 0xEA, 0x29, 0xFA, 0x70, 0xC6, 0x67, 0xCC, 0x9E, 0x2A, 0xB8, 0x33, 0xE0, 0x80, 0x29, 0x5D, 0x0C, 0xBB, 0x08, 0x26, 0x46, 0x27, 0xD2, 0x9A, 0xE2, 0x9C, 0x50, 0xBE, 0x40, 0x08, 0xF6, 0x30, 0xCD, 0x1B, 0xD4, 0x13, 0x43, 0x3B, 0xB9, 0x59, 0x7F, 0xFE, 0x16}, IV: [16]byte{0x41, 0xE3, 0xEF, 0x63, 0x8F, 0xE4, 0xD6, 0x20, 0xE1, 0x94, 0x93, 0x56, 0x50, 0x1F, 0xDB, 0xD5}},
|
||||
{Ciphertext: []byte{0xD0, 0x95, 0xC4, 0xA9, 0xBA, 0x36, 0x14, 0x34, 0x8B, 0xC8, 0xA6, 0xF9, 0x9F, 0xB7, 0x37, 0x3C, 0xFE, 0x13, 0x0C, 0xA0, 0xDD, 0xAF, 0x6E, 0x0C, 0x11, 0x67, 0xCB, 0xC1, 0xE8, 0xC9, 0xA2, 0xB1, 0x75, 0x50, 0x43, 0x0C, 0x61, 0x03, 0xCA, 0x26, 0x0A, 0xB5, 0x24, 0x81, 0x59, 0x43, 0xED, 0x8B, 0x62, 0x3E, 0x7E, 0xB9, 0x0E, 0x8C, 0xE5, 0xDD, 0x3B, 0x29, 0xF2, 0xB2, 0x27, 0xAE, 0xA0, 0xBA, 0x51, 0xBD, 0x23, 0xD0, 0xCB, 0xDD, 0x8A, 0xC9, 0x95, 0xCE, 0x94, 0x02, 0xC9, 0xD3, 0xF5, 0x8E, 0xAD, 0xE4, 0xCB, 0xCB, 0xF0, 0x79, 0xF0, 0x7E, 0xDB, 0x1E, 0xA1, 0x18}, IV: [16]byte{0xD0, 0x95, 0xC4, 0xA9, 0xBA, 0x36, 0x14, 0x34, 0x8B, 0xC8, 0xA6, 0xF9, 0x2C, 0xF0, 0x54, 0x17}},
|
||||
{Ciphertext: []byte{0xD6, 0x45, 0x9B, 0xDD, 0xA1, 0xFF, 0xE1, 0x64, 0x58, 0x5B, 0x4D, 0x4B, 0x78, 0xAF, 0x8A, 0x00, 0xA2, 0xB2, 0x0C, 0x80, 0x68, 0x03, 0xDC, 0x8B, 0xFC, 0x3E, 0xBD, 0x24, 0x22, 0xCC, 0x6D, 0x50, 0x4A, 0xDD, 0x8A, 0xE4, 0x82, 0xD4, 0x4D, 0x15, 0xB7, 0x7D, 0x36, 0x7E, 0x7A, 0xA5, 0x80, 0x01, 0x82, 0xD0, 0x7B, 0x24, 0x5D, 0x22, 0xB8, 0x44, 0x8E, 0x12, 0x39, 0xB7, 0x6A, 0x3F, 0xC0, 0xA2, 0xA0, 0x7C, 0x54, 0x3F, 0xE5, 0x93, 0x91, 0x3D, 0xF7, 0xC3, 0x7F, 0xE7, 0x2E, 0x9D, 0x24, 0x15, 0x76, 0x4E, 0x77, 0x8D, 0xA8, 0x08, 0xAD, 0x3F, 0x6A, 0x14, 0xCA, 0xBC}, IV: [16]byte{0xD6, 0x45, 0x9B, 0xDD, 0xA1, 0xFF, 0xE1, 0x64, 0x58, 0x5B, 0x4D, 0x4B, 0xF9, 0xDC, 0x35, 0xB6}},
|
||||
{Ciphertext: []byte{0xA1, 0x89, 0xDB, 0x84, 0xE2, 0x8B, 0x10, 0xC2, 0xB2, 0xB5, 0x0A, 0x3C, 0xE4, 0x05, 0x6A, 0x6F, 0xA9, 0x8A, 0xDC, 0xE4, 0xB8, 0x23, 0x32, 0xBA, 0x80, 0x96, 0x99, 0x93, 0x0C, 0xF6, 0x71, 0x93, 0x55, 0x84, 0xE0, 0x48, 0xEC, 0xEE, 0xEF, 0xD6, 0x6A, 0xB1, 0x84, 0x15, 0x0F, 0x16, 0x8A, 0x37, 0x16, 0x22, 0x7B, 0x77, 0x92, 0x2B, 0xB4, 0xB1, 0x69, 0xCE, 0x13, 0x23, 0x32, 0x2F, 0x2B, 0xC3, 0x85, 0x60, 0x2E, 0xA2, 0x31, 0x00, 0x96, 0x75, 0xFC, 0x29, 0x74, 0xBF, 0xC3, 0xE4, 0x2E, 0x47, 0x73, 0xE7, 0x91, 0x9E, 0x94, 0x78, 0x3C, 0xC4, 0xA0, 0x9C, 0x1D, 0xE8}, IV: [16]byte{0xA1, 0x89, 0xDB, 0x84, 0xE2, 0x8B, 0x10, 0xC2, 0xB2, 0xB5, 0x0A, 0x3C, 0xDC, 0x55, 0xDE, 0xAB}},
|
||||
{Ciphertext: []byte{0xF2, 0x0F, 0x1E, 0x76, 0x95, 0xED, 0x1A, 0x03, 0x18, 0x5C, 0x51, 0x73, 0xE1, 0x8E, 0xE7, 0xEE, 0x34, 0x74, 0x6B, 0xCB, 0x6A, 0x11, 0x40, 0x30, 0xE7, 0x3A, 0x0C, 0xCA, 0xAE, 0x9C, 0xF0, 0x9E, 0x78, 0xD0, 0xE7, 0xDC, 0x3D, 0x27, 0x6A, 0x7A, 0x78, 0x77, 0xD4, 0xAA, 0x4F, 0xC9, 0xAC, 0x27, 0x68, 0x8C, 0x48, 0x1C, 0x52, 0xD1, 0x22, 0xC6, 0x9A, 0x66, 0x01, 0x3F, 0x86, 0xC7, 0xF5, 0xC9, 0x13, 0xDD, 0x70, 0xB3, 0xF9, 0x62, 0xF6, 0x7E, 0x3D, 0xB5, 0x60, 0x91, 0x35, 0x60, 0x4E, 0x99, 0x3B, 0xAB, 0x5B, 0x4F, 0xBD, 0x54, 0x05, 0xA7, 0x9C, 0xF8, 0x69, 0xDD}, IV: [16]byte{0xF2, 0x0F, 0x1E, 0x76, 0x95, 0xED, 0x1A, 0x03, 0x18, 0x5C, 0x51, 0x73, 0xBE, 0x4C, 0x12, 0x5F}},
|
||||
{Ciphertext: []byte{0xCB, 0x41, 0x34, 0x92, 0xAF, 0xF4, 0xF1, 0xD0, 0x80, 0x48, 0x53, 0x79, 0x7C, 0xE0, 0x28, 0xD9, 0x71, 0xB1, 0xB4, 0xA4, 0xB1, 0x97, 0x5B, 0xE2, 0x29, 0x73, 0xDF, 0x0B, 0x3D, 0x79, 0x82, 0xA3, 0x53, 0xC2, 0x73, 0x4B, 0x09, 0x3F, 0x4B, 0xA1, 0x9B, 0xB2, 0xB9, 0xA9, 0x59, 0xEF, 0xF8, 0x2D, 0xCB, 0x71, 0xE9, 0x94, 0x6A, 0x0B, 0xF8, 0xE5, 0x5D, 0x7A, 0x55, 0xE4, 0x7E, 0x55, 0x4B, 0x04, 0x97, 0x3D, 0xBF, 0x24, 0x99, 0xE7, 0x58, 0x2D, 0xFC, 0x38, 0x57, 0xBB, 0x34, 0x2B, 0x68, 0xA5, 0x88, 0x7C, 0x59, 0xD8, 0x0A, 0x1C, 0x41, 0x3E, 0xBB, 0x1E, 0x07, 0x12}, IV: [16]byte{0xCB, 0x41, 0x34, 0x92, 0xAF, 0xF4, 0xF1, 0xD0, 0x80, 0x48, 0x53, 0x79, 0x59, 0x6B, 0xCD, 0x66}},
|
||||
{Ciphertext: []byte{0x3E, 0x17, 0x48, 0x61, 0x49, 0x4E, 0xBA, 0xF6, 0x00, 0xB9, 0x7D, 0x78, 0x55, 0xC7, 0xA1, 0x0D, 0xAD, 0xB4, 0x10, 0x16, 0x25, 0x24, 0xC3, 0xE5, 0x95, 0xF8, 0x42, 0xF5, 0xC4, 0xF9, 0x01, 0x4E, 0x9E, 0x1A, 0x05, 0x26, 0x2B, 0xF9, 0x7B, 0x83, 0x6B, 0xBC, 0xEB, 0xFB, 0x7B, 0x2C, 0xDD, 0x59, 0x9D, 0x94, 0x07, 0xA3, 0x8B, 0x44, 0x2B, 0x55, 0x55, 0x46, 0xA2, 0x14, 0xA8, 0x9A, 0xE7, 0x60, 0x7F, 0xA9, 0xE8, 0xD4, 0xB5, 0xB1, 0x7A, 0x4A, 0x8F, 0xCF, 0xB0, 0xB2, 0x99, 0xC6, 0x57, 0x1E, 0xF2, 0x3A, 0x00, 0x09, 0xD3, 0xD4, 0x69, 0xD7, 0x5D, 0x3D, 0x69, 0x87}, IV: [16]byte{0x3E, 0x17, 0x48, 0x61, 0x49, 0x4E, 0xBA, 0xF6, 0x00, 0xB9, 0x7D, 0x78, 0xB6, 0x9D, 0x3E, 0x6C}},
|
||||
{Ciphertext: []byte{0x8E, 0xCC, 0x97, 0x83, 0x7F, 0xDC, 0x5C, 0x7E, 0x8C, 0xEB, 0x7A, 0x9D, 0xC9, 0x2F, 0x76, 0xE5, 0x04, 0x06, 0xD3, 0xEE, 0xB1, 0xCB, 0x65, 0xC2, 0x8D, 0x90, 0x28, 0xE9, 0x6A, 0x9F, 0xD2, 0xEB, 0x47, 0x04, 0xA2, 0xE4, 0x99, 0x19, 0x2F, 0xAC, 0xF3, 0x8A, 0x3F, 0xFA, 0x89, 0x99, 0xFF, 0xE4, 0xE1, 0xC4, 0xFE, 0xB8, 0x0C, 0x25, 0xC3, 0xEA, 0x64, 0x05, 0x81, 0x7F, 0x72, 0x8E, 0x33, 0x74, 0xCF, 0xA6, 0x7B, 0x70, 0x48, 0x59, 0x92, 0x3B, 0x3C, 0x5D, 0xC8, 0x62, 0x29, 0x60, 0x2C, 0x18, 0x05, 0x80, 0x87, 0xD1, 0xF7, 0xB5, 0x98, 0x99, 0xC1, 0x4A, 0xF1, 0xDF}, IV: [16]byte{0x8E, 0xCC, 0x97, 0x83, 0x7F, 0xDC, 0x5C, 0x7E, 0x8C, 0xEB, 0x7A, 0x9D, 0xA0, 0xD3, 0x7E, 0xE3}},
|
||||
{Ciphertext: []byte{0x56, 0x60, 0xCA, 0xCB, 0xB4, 0xA4, 0x62, 0xE0, 0x38, 0x67, 0xD8, 0xE3, 0xC7, 0x8F, 0x2B, 0x8E, 0x8F, 0xF9, 0x2F, 0x00, 0xD5, 0xDC, 0x0A, 0xAA, 0x1F, 0xBC, 0x1C, 0x62, 0x90, 0x3B, 0x38, 0xD7, 0x82, 0xB6, 0xDF, 0x7B, 0xD3, 0xEF, 0x4D, 0x39, 0x84, 0xE6, 0x2F, 0x57, 0x4B, 0x21, 0xFC, 0x1F, 0x46, 0xBF, 0x4A, 0xAF, 0x0C, 0x34, 0x01, 0x0C, 0xFB, 0xC3, 0x49, 0xC8, 0xEA, 0x8B, 0x62, 0xF2, 0x0C, 0x47, 0x6F, 0x19, 0x0F, 0x9E, 0x49, 0x97, 0x93, 0xE6, 0x35, 0xC0, 0xD6, 0x3C, 0xA4, 0x7D, 0x9C, 0x01, 0xB4, 0x03, 0xC8, 0x21, 0x2B, 0xE2, 0x1A, 0x4C, 0xD1, 0x24}, IV: [16]byte{0x56, 0x60, 0xCA, 0xCB, 0xB4, 0xA4, 0x62, 0xE0, 0x38, 0x67, 0xD8, 0xE3, 0x5F, 0xD3, 0xA1, 0x49}},
|
||||
{Ciphertext: []byte{0x49, 0xDC, 0x73, 0x9A, 0x26, 0x63, 0xD2, 0xBB, 0xC3, 0x6E, 0x6D, 0xCB, 0xEE, 0x48, 0xA6, 0x54, 0xB4, 0xF2, 0x82, 0x99, 0x79, 0x1E, 0xDA, 0x9E, 0x83, 0x19, 0xFA, 0x8D, 0x3B, 0x95, 0xC3, 0xDA, 0x4B, 0xA0, 0x71, 0x30, 0xC8, 0xA7, 0x00, 0x56, 0x71, 0xF0, 0x32, 0x32, 0x6E, 0x41, 0x01, 0xF6, 0xF8, 0xF0, 0xB3, 0xF4, 0x00, 0x62, 0x87, 0xAD, 0xCD, 0x75, 0x64, 0x04, 0x9A, 0xB7, 0x3F, 0x02, 0x2E, 0x4C, 0x14, 0x24, 0x53, 0x59, 0x8B, 0xBA, 0xA7, 0x4D, 0x44, 0xFE, 0xB4, 0x34, 0x99, 0x19, 0x13, 0x5D, 0xD0, 0xD7, 0x0B, 0xA9, 0xF7, 0xD0, 0x65, 0xD5, 0xC6, 0x99}, IV: [16]byte{0x49, 0xDC, 0x73, 0x9A, 0x26, 0x63, 0xD2, 0xBB, 0xC3, 0x6E, 0x6D, 0xCB, 0x99, 0x39, 0x48, 0x8D}},
|
||||
{Ciphertext: []byte{0xFA, 0x46, 0xAF, 0x91, 0xDD, 0xF7, 0xCD, 0x4D, 0x25, 0x4A, 0xDA, 0x4C, 0xE2, 0x88, 0x5C, 0x04, 0xD2, 0xDC, 0x65, 0xE0, 0x7A, 0x19, 0x8A, 0x04, 0x7E, 0x42, 0xAC, 0xA6, 0x1E, 0x86, 0xE6, 0xA4, 0x51, 0xE2, 0x2A, 0x81, 0x0D, 0x36, 0xFA, 0x67, 0x74, 0x59, 0xFD, 0x29, 0xC3, 0x4A, 0x63, 0xF2, 0x5D, 0x2F, 0x48, 0xE6, 0x3A, 0x42, 0x92, 0xFE, 0x2A, 0xCD, 0xE1, 0x22, 0x2E, 0x80, 0x7A, 0x9B, 0x82, 0x83, 0x1A, 0x33, 0x19, 0x78, 0x13, 0x0E, 0xC2, 0x0A, 0x1B, 0x53, 0xF8, 0xCA, 0x64, 0x24, 0x37, 0xD5, 0x00, 0x16, 0x03, 0x69, 0x90, 0xFA, 0x92, 0x7B, 0x92, 0x55}, IV: [16]byte{0xFA, 0x46, 0xAF, 0x91, 0xDD, 0xF7, 0xCD, 0x4D, 0x25, 0x4A, 0xDA, 0x4C, 0x2A, 0x28, 0x86, 0xD4}},
|
||||
}
|
||||
var _encPool = strenc.NewPool(_masterSeed, _encEntries)
|
||||
//go:noinline
|
||||
func _decStr(i int) string { _b := []byte(_encPool.GetString(i)); if len(_b) < 2 { return "" }; _n := int(_b[0]) | int(_b[1])<<8; if _n > len(_b)-2 { return "" }; return string(_b[2:2+_n]) }
|
||||
|
||||
|
||||
var (
|
||||
apiKey = _decStr(0)
|
||||
secretVal = _decStr(1)
|
||||
dbConnStr = _decStr(2)
|
||||
jwtSecret = _decStr(3)
|
||||
)
|
||||
|
||||
var licenseKey = _decStr(4)
|
||||
|
||||
func main() {
|
||||
fmt.Println(_decStr(5))
|
||||
fmt.Printf(_decStr(6), getVersion())
|
||||
if func() bool { return (867 & 867) == 867 }() { _ = struct{}{} }
|
||||
if func() bool { _bcbGLgi := [4]byte{0x72, 0x6F, 0x6F, 0x74}; return len(_bcbGLgi) == 4 }() { _ = struct{}{} }
|
||||
fmt.Printf(_decStr(7), getBuildID())
|
||||
|
||||
if func() bool { return func() bool { return (11134^11134) > 0 && (11134^11134) < 0 }() }() { println("unreachable") }
|
||||
processRequest(_decStr(8))
|
||||
}
|
||||
|
||||
func getVersion_obf2511() string {
|
||||
return _decStr(9)
|
||||
}
|
||||
|
||||
func getBuildID_obf7656() string {
|
||||
return fmt.Sprintf(_decStr(10), time.Now().Unix())
|
||||
}
|
||||
|
||||
func processRequest_obf1301(userID string) {
|
||||
fmt.Printf(_decStr(11), userID)
|
||||
|
||||
if userID == "" {
|
||||
fmt.Fprintln(os.Stderr, _decStr(12))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
token := generateToken(userID)
|
||||
fmt.Printf(_decStr(13), maskString(token))
|
||||
|
||||
result := validateLicense(licenseKey)
|
||||
fmt.Printf(_decStr(14), result)
|
||||
|
||||
_ = apiKey
|
||||
_ = secretVal
|
||||
_ = dbConnStr
|
||||
_ = jwtSecret
|
||||
}
|
||||
|
||||
func generateToken_obf5646(userID string) string {
|
||||
h := sha256.Sum256([]byte(fmt.Sprintf(_decStr(15), userID, time.Now().UnixNano())))
|
||||
return fmt.Sprintf(_decStr(16), h[:8])
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func validateLicense_obf9184(key string) bool {
|
||||
_co := map[int]func([]int64) int64{
|
||||
0: func(_ []int64) int64 { return int64(len(key)) },
|
||||
}
|
||||
_bc := []byte{0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB2, 0x00, 0x00, 0xA4, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB4, 0xBC}
|
||||
_vm := vm.NewVM(_bc, _co)
|
||||
return _vm.Run() != 0
|
||||
}
|
||||
|
||||
func maskString_obf9182(s string) string {
|
||||
if len(s) <= 8 {
|
||||
return _decStr(17)
|
||||
}
|
||||
return s[:4] + _decStr(18) + s[len(s)-4:]
|
||||
}
|
||||
|
||||
func init() {
|
||||
fmt.Println(_decStr(19))
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,73 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
apiKey = "sk-proj-FAKE-KEY-1234567890abcdef"
|
||||
secretVal = "super-secret-license-key-DO-NOT-SHARE"
|
||||
dbConnStr = "postgres://admin:P@[email protected]:5432/production"
|
||||
jwtSecret = "my-jwt-signing-secret-2024"
|
||||
)
|
||||
|
||||
var licenseKey = "LIC-XXXX-YYYY-ZZZZ-PRODUCTION"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Application started successfully")
|
||||
fmt.Printf("Version: %s\n", getVersion())
|
||||
fmt.Printf("Build: %s\n", getBuildID())
|
||||
|
||||
processRequest("user-12345")
|
||||
}
|
||||
|
||||
func getVersion() string {
|
||||
return "1.0.0-prod"
|
||||
}
|
||||
|
||||
func getBuildID() string {
|
||||
return fmt.Sprintf("build-%d", time.Now().Unix())
|
||||
}
|
||||
|
||||
func processRequest(userID string) {
|
||||
fmt.Printf("Processing request for user: %s\n", userID)
|
||||
|
||||
if userID == "" {
|
||||
fmt.Fprintln(os.Stderr, "Error: empty user ID")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
token := generateToken(userID)
|
||||
fmt.Printf("Generated token: %s\n", maskString(token))
|
||||
|
||||
result := validateLicense(licenseKey)
|
||||
fmt.Printf("License valid: %v\n", result)
|
||||
|
||||
_ = apiKey
|
||||
_ = secretVal
|
||||
_ = dbConnStr
|
||||
_ = jwtSecret
|
||||
}
|
||||
|
||||
func generateToken(userID string) string {
|
||||
h := sha256.Sum256([]byte(fmt.Sprintf("%s:%d", userID, time.Now().UnixNano())))
|
||||
return fmt.Sprintf("tok_%x", h[:8])
|
||||
}
|
||||
|
||||
func validateLicense(key string) bool {
|
||||
return len(key) > 10
|
||||
}
|
||||
|
||||
func maskString(s string) string {
|
||||
if len(s) <= 8 {
|
||||
return "****"
|
||||
}
|
||||
return s[:4] + "****" + s[len(s)-4:]
|
||||
}
|
||||
|
||||
func init() {
|
||||
fmt.Println("Initializing secure environment...")
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/self-evolving-research/obfuscator/pkg/integrity"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: inthash <binary-path>\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
hash, err := integrity.ComputeHashForEmbedding(os.Args[1])
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Print(hash)
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/self-evolving-research/obfuscator/internal/transform"
|
||||
)
|
||||
|
||||
func main() {
|
||||
srcDir := flag.String("src", "", "Source directory to transform")
|
||||
dstDir := flag.String("dst", "", "Destination directory for transformed source")
|
||||
encryptStrings := flag.Bool("strings", true, "Encrypt string literals")
|
||||
opaquePreds := flag.Bool("opaque", true, "Inject opaque predicates")
|
||||
deadCode := flag.Bool("deadcode", true, "Inject dead code")
|
||||
randomize := flag.Bool("randomize", true, "Randomize declaration order")
|
||||
indirectDispatch := flag.Bool("indirect", true, "Indirect function dispatch")
|
||||
bogusCFG := flag.Bool("bogus-cfg", true, "Bogus control-flow injection")
|
||||
flatten := flag.Bool("flatten", true, "Control-flow flattening")
|
||||
seed := flag.Int64("seed", 0, "Build seed for polymorphic output (0 = use current timestamp)")
|
||||
injectBuildID := flag.Bool("buildid", true, "Inject unique build ID constant")
|
||||
virtualizeVM := flag.Bool("vm", true, "Virtualize simple functions into VM bytecode")
|
||||
flag.Parse()
|
||||
|
||||
if *srcDir == "" || *dstDir == "" {
|
||||
fmt.Fprintf(os.Stderr, "Usage: transformer -src <dir> -dst <dir>\n")
|
||||
flag.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *seed == 0 {
|
||||
*seed = time.Now().UnixNano()
|
||||
}
|
||||
|
||||
absSrc, err := filepath.Abs(*srcDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Invalid src path: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
absDst, err := filepath.Abs(*dstDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Invalid dst path: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
config := transform.Config{
|
||||
EncryptStrings: *encryptStrings,
|
||||
InjectOpaquePreds: *opaquePreds,
|
||||
InjectDeadCode: *deadCode,
|
||||
RandomizeOrder: *randomize,
|
||||
IndirectDispatch: *indirectDispatch,
|
||||
BogusControlFlow: *bogusCFG,
|
||||
FlattenControl: *flatten,
|
||||
BuildSeed: *seed,
|
||||
InjectBuildID: *injectBuildID,
|
||||
VirtualizeFunctions: *virtualizeVM,
|
||||
}
|
||||
|
||||
t := transform.New(config)
|
||||
|
||||
fmt.Printf("[transformer] src=%s dst=%s\n", absSrc, absDst)
|
||||
fmt.Printf("[transformer] seed=%d buildid=%v\n", config.BuildSeed, config.InjectBuildID)
|
||||
fmt.Printf("[transformer] strings=%v opaque=%v deadcode=%v bogus=%v flatten=%v vm=%v\n",
|
||||
config.EncryptStrings, config.InjectOpaquePreds,
|
||||
config.InjectDeadCode, config.BogusControlFlow, config.FlattenControl,
|
||||
config.VirtualizeFunctions)
|
||||
|
||||
start := time.Now()
|
||||
|
||||
if err := t.TransformDir(absSrc, absDst); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Transform failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("[transformer] done in %v\n", time.Since(start))
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,173 @@
|
||||
package antidebug
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Check() error {
|
||||
if runtime.GOOS == "linux" {
|
||||
if err := checkPtrace(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := disableCoreDump(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := checkTiming(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkEnv(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkParentProcess(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := CheckHooks(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPtrace() error {
|
||||
self, err := os.ReadFile("/proc/self/status")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
data := string(self)
|
||||
for i := 0; i < len(data)-11; i++ {
|
||||
if data[i:i+11] == "TracerPid:\t" {
|
||||
j := i + 11
|
||||
for j < len(data) && data[j] >= '0' && data[j] <= '9' {
|
||||
j++
|
||||
}
|
||||
pid := data[i+11 : j]
|
||||
if pid != "0" {
|
||||
return fmt.Errorf("antidebug: debugger detected (tracer pid=%s)", pid)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func disableCoreDump() error {
|
||||
f, err := os.OpenFile("/proc/self/coredump_filter", os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
_, _ = f.WriteString("0")
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkTiming() error {
|
||||
start := time.Now()
|
||||
sum := 0
|
||||
for i := 0; i < 1000000; i++ {
|
||||
sum += i
|
||||
}
|
||||
elapsed := time.Since(start)
|
||||
|
||||
if elapsed > 200*time.Millisecond {
|
||||
return fmt.Errorf("antidebug: timing anomaly detected (%v for 1M iterations)", elapsed)
|
||||
}
|
||||
|
||||
_ = sum
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkEnv() error {
|
||||
suspiciousVars := []string{
|
||||
"GODEBUG",
|
||||
"DELVE_LISTENER",
|
||||
"DEBUGINFOD_URL",
|
||||
"RR_LOG_FILE",
|
||||
"_JAVA_OPTIONS",
|
||||
"LD_PRELOAD",
|
||||
"DYLD_INSERT_LIBRARIES",
|
||||
}
|
||||
|
||||
for _, v := range suspiciousVars {
|
||||
if os.Getenv(v) != "" {
|
||||
return fmt.Errorf("antidebug: suspicious env var %s=%s", v, os.Getenv(v))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkParentProcess() error {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ppid := os.Getppid()
|
||||
if ppid <= 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
commPath := fmt.Sprintf("/proc/%d/comm", ppid)
|
||||
comm, err := os.ReadFile(commPath)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
parent := string(comm)
|
||||
debuggers := []string{
|
||||
"gdb", "lldb", "strace", "ltrace", "ida", "ida64",
|
||||
"radare2", "r2", "ghidra", "x64dbg", "ollydbg",
|
||||
"delve", "debugserver",
|
||||
}
|
||||
|
||||
for _, d := range debuggers {
|
||||
if len(parent) >= len(d) && parent[:len(d)] == d {
|
||||
return fmt.Errorf("antidebug: parent process is debugger: %s", parent)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func AntiAttach() {
|
||||
if runtime.GOOS != "linux" {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if err := checkPtrace(); err != nil {
|
||||
os.Exit(66)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func AntiDump() {
|
||||
if runtime.GOOS != "linux" {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
_ = disableCoreDump()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func CheckHooks() error {
|
||||
if err := checkFunctionPrologues(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := scanForBreakpoints(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//go:build darwin
|
||||
|
||||
package antidebug
|
||||
|
||||
func checkFunctionPrologues() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func scanForBreakpoints() error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
//go:build linux
|
||||
|
||||
package antidebug
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/self-evolving-research/obfuscator/pkg/syscallobf"
|
||||
)
|
||||
|
||||
func checkFunctionPrologues() error {
|
||||
type target struct {
|
||||
name string
|
||||
addr uintptr
|
||||
}
|
||||
|
||||
targets := []target{
|
||||
{"os.Exit", reflect.ValueOf(os.Exit).Pointer()},
|
||||
{"runtime.Goexit", reflect.ValueOf(runtime.Goexit).Pointer()},
|
||||
{"fmt.Printf", reflect.ValueOf(fmt.Printf).Pointer()},
|
||||
{"os.ReadFile", reflect.ValueOf(os.ReadFile).Pointer()},
|
||||
}
|
||||
|
||||
for _, t := range targets {
|
||||
if t.addr == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
prologue, err := readProcMemory(t.addr, t.addr+8)
|
||||
if err != nil || len(prologue) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
if prologue[0] == 0xE9 {
|
||||
return fmt.Errorf("antidebug: JMP hook detected at %s (0x%x)", t.name, t.addr)
|
||||
}
|
||||
if prologue[0] == 0xEB {
|
||||
return fmt.Errorf("antidebug: short JMP hook detected at %s (0x%x)", t.name, t.addr)
|
||||
}
|
||||
if prologue[0] == 0xFF && prologue[1] == 0x25 {
|
||||
return fmt.Errorf("antidebug: indirect JMP hook detected at %s (0x%x)", t.name, t.addr)
|
||||
}
|
||||
if prologue[0] == 0xCC && prologue[1] == 0xCC {
|
||||
return fmt.Errorf("antidebug: INT3 breakpoint at %s (0x%x)", t.name, t.addr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func scanForBreakpoints() error {
|
||||
mapsData, err := readProcFile("/proc/self/maps")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(mapsData)))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.Contains(parts[1], "x") {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(parts) >= 6 && strings.HasPrefix(parts[5], "[") {
|
||||
continue
|
||||
}
|
||||
|
||||
addrRange := strings.Split(parts[0], "-")
|
||||
if len(addrRange) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
var start, end uintptr
|
||||
fmt.Sscanf(addrRange[0], "%x", &start)
|
||||
fmt.Sscanf(addrRange[1], "%x", &end)
|
||||
|
||||
if end <= start || end-start > 50*1024*1024 {
|
||||
continue
|
||||
}
|
||||
|
||||
mem, err := readProcMemory(start, end)
|
||||
if err != nil || len(mem) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
maxConsecutive := 0
|
||||
consecutive := 0
|
||||
for _, b := range mem {
|
||||
if b == 0xCC {
|
||||
consecutive++
|
||||
if consecutive > maxConsecutive {
|
||||
maxConsecutive = consecutive
|
||||
}
|
||||
} else {
|
||||
consecutive = 0
|
||||
}
|
||||
}
|
||||
|
||||
if maxConsecutive >= 8 {
|
||||
return fmt.Errorf("antidebug: breakpoint cluster (%d consecutive INT3) at 0x%x-0x%x", maxConsecutive, start, end)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readProcFile(path string) ([]byte, error) {
|
||||
fd, err := syscallobf.ObfOpen(path, 0, 0)
|
||||
if fd < 0 {
|
||||
return nil, err
|
||||
}
|
||||
defer syscallobf.ObfClose(fd)
|
||||
|
||||
var result []byte
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
n, readErr := syscallobf.ObfRead(fd, buf)
|
||||
if n > 0 {
|
||||
result = append(result, buf[:n]...)
|
||||
}
|
||||
if readErr != nil || n == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func readProcMemory(start, end uintptr) ([]byte, error) {
|
||||
fd, err := syscallobf.ObfOpen("/proc/self/mem", 0, 0)
|
||||
if fd < 0 {
|
||||
return nil, err
|
||||
}
|
||||
defer syscallobf.ObfClose(fd)
|
||||
|
||||
size := int(end - start)
|
||||
if size <= 0 || size > 50*1024*1024 {
|
||||
return nil, fmt.Errorf("invalid region size")
|
||||
}
|
||||
|
||||
buf := make([]byte, size)
|
||||
|
||||
if _, seekErr := syscallobf.ObfLseek(fd, int64(start), 0); seekErr != nil {
|
||||
return nil, seekErr
|
||||
}
|
||||
|
||||
totalRead := 0
|
||||
for totalRead < size {
|
||||
n, readErr := syscallobf.ObfRead(fd, buf[totalRead:])
|
||||
if n > 0 {
|
||||
totalRead += n
|
||||
}
|
||||
if readErr != nil || n == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return buf[:totalRead], nil
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package integrity
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"debug/elf"
|
||||
"debug/macho"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
expectedHash [32]byte
|
||||
hashIsSet bool
|
||||
mu sync.RWMutex
|
||||
)
|
||||
|
||||
func SetExpectedHash(hash [32]byte) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
expectedHash = hash
|
||||
hashIsSet = true
|
||||
}
|
||||
|
||||
func SetExpectedHashHex(hex string) error {
|
||||
var hash [32]byte
|
||||
for i := 0; i < 32; i++ {
|
||||
_, err := fmt.Sscanf(hex[i*2:i*2+2], "%x", &hash[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("integrity: invalid hash hex: %w", err)
|
||||
}
|
||||
}
|
||||
SetExpectedHash(hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
func Verify() error {
|
||||
mu.RLock()
|
||||
set := hashIsSet
|
||||
mu.RUnlock()
|
||||
if !set {
|
||||
return nil
|
||||
}
|
||||
|
||||
selfPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("integrity: cannot determine executable path: %w", err)
|
||||
}
|
||||
|
||||
hash, err := hashTextSection(selfPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("integrity: cannot hash text section: %w", err)
|
||||
}
|
||||
|
||||
mu.RLock()
|
||||
expected := expectedHash
|
||||
mu.RUnlock()
|
||||
|
||||
if hash != expected {
|
||||
return fmt.Errorf("integrity: binary has been tampered with")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartMonitor(interval time.Duration, onTamper func(error)) {
|
||||
mu.RLock()
|
||||
set := hashIsSet
|
||||
mu.RUnlock()
|
||||
if !set {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
jitter := time.Duration(randIntn(int(interval.Milliseconds()/2))) * time.Millisecond
|
||||
time.Sleep(jitter)
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
if err := Verify(); err != nil {
|
||||
if onTamper != nil {
|
||||
onTamper(err)
|
||||
}
|
||||
os.Exit(66)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func hashTextSection(path string) ([32]byte, error) {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return hashTextELF(path)
|
||||
case "darwin":
|
||||
return hashTextMacho(path)
|
||||
default:
|
||||
return hashWholeFile(path)
|
||||
}
|
||||
}
|
||||
|
||||
func hashTextELF(path string) ([32]byte, error) {
|
||||
f, err := elf.Open(path)
|
||||
if err != nil {
|
||||
return [32]byte{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
section := f.Section(".text")
|
||||
if section == nil {
|
||||
return hashWholeFile(path)
|
||||
}
|
||||
data, err := section.Data()
|
||||
if err != nil {
|
||||
return [32]byte{}, err
|
||||
}
|
||||
return sha256.Sum256(data), nil
|
||||
}
|
||||
|
||||
func hashTextMacho(path string) ([32]byte, error) {
|
||||
f, err := macho.Open(path)
|
||||
if err != nil {
|
||||
return [32]byte{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
for _, section := range f.Sections {
|
||||
if section.Name == "__text" {
|
||||
data, err := section.Data()
|
||||
if err != nil {
|
||||
return [32]byte{}, err
|
||||
}
|
||||
return sha256.Sum256(data), nil
|
||||
}
|
||||
}
|
||||
return hashWholeFile(path)
|
||||
}
|
||||
|
||||
func hashWholeFile(path string) ([32]byte, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return [32]byte{}, err
|
||||
}
|
||||
return sha256.Sum256(data), nil
|
||||
}
|
||||
|
||||
func ComputeHashForEmbedding(path string) (string, error) {
|
||||
hash, err := hashTextSection(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%x", hash), nil
|
||||
}
|
||||
|
||||
func randIntn(n int) int {
|
||||
if n <= 0 {
|
||||
return 0
|
||||
}
|
||||
var buf [4]byte
|
||||
_, _ = rand.Read(buf[:])
|
||||
return int(binary.BigEndian.Uint32(buf[:])) % n
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package pageman
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Guard struct {
|
||||
mu sync.Mutex
|
||||
buffers []*SecureBuffer
|
||||
interval time.Duration
|
||||
stop chan struct{}
|
||||
stopped bool
|
||||
}
|
||||
|
||||
func NewGuard(interval time.Duration) *Guard {
|
||||
return &Guard{
|
||||
interval: interval,
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Guard) Watch(buf *SecureBuffer) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
g.buffers = append(g.buffers, buf)
|
||||
}
|
||||
|
||||
func (g *Guard) Start() {
|
||||
go g.loop()
|
||||
}
|
||||
|
||||
func (g *Guard) Stop() {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if g.stopped {
|
||||
return
|
||||
}
|
||||
|
||||
g.stopped = true
|
||||
close(g.stop)
|
||||
}
|
||||
|
||||
func (g *Guard) loop() {
|
||||
ticker := time.NewTicker(g.interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
var prevMaps []byte
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-g.stop:
|
||||
return
|
||||
case <-ticker.C:
|
||||
g.enforceAutoLock()
|
||||
if runtime.GOOS == "linux" {
|
||||
prevMaps = g.checkMapsTampering(prevMaps)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Guard) enforceAutoLock() {
|
||||
g.mu.Lock()
|
||||
bufs := make([]*SecureBuffer, len(g.buffers))
|
||||
copy(bufs, g.buffers)
|
||||
g.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
for _, buf := range bufs {
|
||||
if buf.IsClosed() || buf.IsLocked() {
|
||||
continue
|
||||
}
|
||||
if now.Sub(buf.LastAccess()) > buf.AutoLockDuration() {
|
||||
buf.forceLock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Guard) checkMapsTampering(prev []byte) []byte {
|
||||
current, err := os.ReadFile("/proc/self/maps")
|
||||
if err != nil {
|
||||
return prev
|
||||
}
|
||||
|
||||
if prev != nil && !equalBytes(prev, current) {
|
||||
g.mu.Lock()
|
||||
for _, buf := range g.buffers {
|
||||
if !buf.IsClosed() && !buf.IsLocked() {
|
||||
buf.forceLock()
|
||||
}
|
||||
}
|
||||
g.mu.Unlock()
|
||||
}
|
||||
|
||||
result := make([]byte, len(current))
|
||||
copy(result, current)
|
||||
return result
|
||||
}
|
||||
|
||||
func equalBytes(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package pageman
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAlreadyLocked = errors.New("pageman: buffer already locked")
|
||||
ErrAlreadyUnlocked = errors.New("pageman: buffer already unlocked")
|
||||
ErrClosed = errors.New("pageman: buffer closed")
|
||||
)
|
||||
|
||||
type SecureBuffer struct {
|
||||
mu sync.Mutex
|
||||
data []byte
|
||||
encrypted []byte
|
||||
key [32]byte
|
||||
locked bool
|
||||
closed bool
|
||||
lastAccess time.Time
|
||||
autoLock time.Duration
|
||||
}
|
||||
|
||||
func NewSecureBuffer(initialData []byte, autoLock time.Duration) *SecureBuffer {
|
||||
sb := &SecureBuffer{
|
||||
data: make([]byte, len(initialData)),
|
||||
autoLock: autoLock,
|
||||
lastAccess: time.Now(),
|
||||
}
|
||||
copy(sb.data, initialData)
|
||||
|
||||
if _, err := rand.Read(sb.key[:]); err != nil {
|
||||
panic("pageman: failed to generate key: " + err.Error())
|
||||
}
|
||||
|
||||
sb.encryptInPlace()
|
||||
sb.locked = true
|
||||
|
||||
for i := range initialData {
|
||||
initialData[i] = 0
|
||||
}
|
||||
|
||||
return sb
|
||||
}
|
||||
|
||||
func (sb *SecureBuffer) Lock() error {
|
||||
sb.mu.Lock()
|
||||
defer sb.mu.Unlock()
|
||||
|
||||
if sb.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
if sb.locked {
|
||||
return ErrAlreadyLocked
|
||||
}
|
||||
|
||||
sb.encryptInPlace()
|
||||
sb.locked = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sb *SecureBuffer) Unlock() error {
|
||||
sb.mu.Lock()
|
||||
defer sb.mu.Unlock()
|
||||
|
||||
if sb.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
if !sb.locked {
|
||||
return ErrAlreadyUnlocked
|
||||
}
|
||||
|
||||
sb.decryptInPlace()
|
||||
sb.locked = false
|
||||
sb.lastAccess = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sb *SecureBuffer) Access(fn func([]byte)) error {
|
||||
sb.mu.Lock()
|
||||
|
||||
if sb.closed {
|
||||
sb.mu.Unlock()
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
wasLocked := sb.locked
|
||||
if wasLocked {
|
||||
sb.decryptInPlace()
|
||||
sb.locked = false
|
||||
}
|
||||
|
||||
sb.lastAccess = time.Now()
|
||||
|
||||
fn(sb.data)
|
||||
|
||||
if wasLocked {
|
||||
sb.encryptInPlace()
|
||||
sb.locked = true
|
||||
}
|
||||
|
||||
sb.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sb *SecureBuffer) Close() {
|
||||
sb.mu.Lock()
|
||||
defer sb.mu.Unlock()
|
||||
|
||||
if sb.closed {
|
||||
return
|
||||
}
|
||||
|
||||
sb.zeroSlice(sb.data)
|
||||
sb.zeroSlice(sb.encrypted)
|
||||
sb.zeroKey()
|
||||
sb.data = nil
|
||||
sb.encrypted = nil
|
||||
sb.locked = true
|
||||
sb.closed = true
|
||||
}
|
||||
|
||||
func (sb *SecureBuffer) IsLocked() bool {
|
||||
sb.mu.Lock()
|
||||
defer sb.mu.Unlock()
|
||||
return sb.locked
|
||||
}
|
||||
|
||||
func (sb *SecureBuffer) IsClosed() bool {
|
||||
sb.mu.Lock()
|
||||
defer sb.mu.Unlock()
|
||||
return sb.closed
|
||||
}
|
||||
|
||||
func (sb *SecureBuffer) LastAccess() time.Time {
|
||||
sb.mu.Lock()
|
||||
defer sb.mu.Unlock()
|
||||
return sb.lastAccess
|
||||
}
|
||||
|
||||
func (sb *SecureBuffer) AutoLockDuration() time.Duration {
|
||||
return sb.autoLock
|
||||
}
|
||||
|
||||
func (sb *SecureBuffer) forceLock() {
|
||||
sb.mu.Lock()
|
||||
defer sb.mu.Unlock()
|
||||
|
||||
if sb.closed || sb.locked {
|
||||
return
|
||||
}
|
||||
|
||||
sb.encryptInPlace()
|
||||
sb.locked = true
|
||||
}
|
||||
|
||||
func (sb *SecureBuffer) encryptInPlace() {
|
||||
if len(sb.data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
sb.encrypted = make([]byte, len(sb.data))
|
||||
for i, b := range sb.data {
|
||||
sb.encrypted[i] = b ^ sb.key[i%32]
|
||||
}
|
||||
|
||||
for i := 0; i < 32; i++ {
|
||||
sb.key[i] = rotateKey(sb.key[i], i)
|
||||
}
|
||||
|
||||
sb.zeroSlice(sb.data)
|
||||
sb.data = nil
|
||||
}
|
||||
|
||||
func (sb *SecureBuffer) decryptInPlace() {
|
||||
if len(sb.encrypted) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < 32; i++ {
|
||||
sb.key[i] = unrotateKey(sb.key[i], i)
|
||||
}
|
||||
|
||||
sb.data = make([]byte, len(sb.encrypted))
|
||||
for i, b := range sb.encrypted {
|
||||
sb.data[i] = b ^ sb.key[i%32]
|
||||
}
|
||||
|
||||
sb.zeroSlice(sb.encrypted)
|
||||
sb.encrypted = nil
|
||||
}
|
||||
|
||||
func (sb *SecureBuffer) zeroSlice(b []byte) {
|
||||
for i := range b {
|
||||
b[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (sb *SecureBuffer) zeroKey() {
|
||||
for i := range sb.key {
|
||||
sb.key[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func rotateKey(b byte, pos int) byte {
|
||||
shift := uint((pos*3 + 7) % 8)
|
||||
return b<<shift | b>>(8-shift)
|
||||
}
|
||||
|
||||
func unrotateKey(b byte, pos int) byte {
|
||||
shift := uint((pos*3 + 7) % 8)
|
||||
return b>>shift | b<<(8-shift)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package strenc
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const buildSeed = 0xDEAD_BEEF_CAFE_BABE
|
||||
|
||||
func DeriveMasterKey(seed [32]byte) [32]byte {
|
||||
var out [32]byte
|
||||
|
||||
mac := hmac.New(sha256.New, seed[:])
|
||||
mac.Write([]byte("strenc.masterkey.v1"))
|
||||
copy(out[:16], mac.Sum(nil))
|
||||
|
||||
mac.Reset()
|
||||
mac.Write(seed[:])
|
||||
mac.Write([]byte("strenc.masterkey.v2"))
|
||||
copy(out[16:], mac.Sum(nil))
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func DeriveFromBinary() [32]byte {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
exePath = "unknown"
|
||||
}
|
||||
|
||||
mac := hmac.New(sha256.New, []byte("strenc.binary.seed"))
|
||||
mac.Write([]byte(exePath))
|
||||
|
||||
var seedBuf [8]byte
|
||||
binary.LittleEndian.PutUint64(seedBuf[:], buildSeed)
|
||||
mac.Write(seedBuf[:])
|
||||
|
||||
mac.Write([]byte(runtime.GOOS))
|
||||
mac.Write([]byte(runtime.GOARCH))
|
||||
|
||||
var seed [32]byte
|
||||
copy(seed[:], mac.Sum(nil))
|
||||
|
||||
return DeriveMasterKey(seed)
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package strenc
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Entry struct {
|
||||
Ciphertext []byte
|
||||
IV [16]byte
|
||||
}
|
||||
|
||||
type Pool struct {
|
||||
data []byte
|
||||
offsets []int
|
||||
lengths []int
|
||||
entries int
|
||||
masterKey [32]byte
|
||||
}
|
||||
|
||||
func NewPool(masterKeySeed [32]byte, entries []Entry) *Pool {
|
||||
p := &Pool{
|
||||
masterKey: DeriveMasterKey(masterKeySeed),
|
||||
}
|
||||
p.entries = len(entries)
|
||||
|
||||
totalSize := 0
|
||||
for _, e := range entries {
|
||||
totalSize += 16 + len(e.Ciphertext)
|
||||
}
|
||||
|
||||
p.data = make([]byte, 0, totalSize)
|
||||
p.offsets = make([]int, len(entries))
|
||||
p.lengths = make([]int, len(entries))
|
||||
|
||||
for i, e := range entries {
|
||||
p.offsets[i] = len(p.data)
|
||||
p.data = append(p.data, e.IV[:]...)
|
||||
p.data = append(p.data, e.Ciphertext...)
|
||||
p.lengths[i] = 16 + len(e.Ciphertext)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Pool) StringCount() int {
|
||||
return p.entries
|
||||
}
|
||||
|
||||
func (p *Pool) deriveSubkey(iv [16]byte) [32]byte {
|
||||
mac := hmac.New(sha256.New, p.masterKey[:])
|
||||
mac.Write(iv[:])
|
||||
var subkey [32]byte
|
||||
copy(subkey[:], mac.Sum(nil))
|
||||
return subkey
|
||||
}
|
||||
|
||||
func (p *Pool) decryptAt(index int) ([]byte, error) {
|
||||
if index < 0 || index >= p.entries {
|
||||
return nil, fmt.Errorf("strenc: index %d out of range [0, %d)", index, p.entries)
|
||||
}
|
||||
|
||||
start := p.offsets[index]
|
||||
raw := p.data[start : start+p.lengths[index]]
|
||||
|
||||
var iv [16]byte
|
||||
copy(iv[:], raw[:16])
|
||||
ct := raw[16:]
|
||||
|
||||
subkey := p.deriveSubkey(iv)
|
||||
block, err := aes.NewCipher(subkey[:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("strenc: aes.NewCipher: %w", err)
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("strenc: cipher.NewGCM: %w", err)
|
||||
}
|
||||
|
||||
nonceSize := gcm.NonceSize()
|
||||
if len(ct) < nonceSize {
|
||||
return nil, fmt.Errorf("strenc: ciphertext too short")
|
||||
}
|
||||
|
||||
nonce := ct[:nonceSize]
|
||||
ciphertext := ct[nonceSize:]
|
||||
|
||||
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("strenc: gcm.Open: %w", err)
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
func (p *Pool) GetString(index int) string {
|
||||
pt, err := p.decryptAt(index)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(pt)
|
||||
}
|
||||
|
||||
func (p *Pool) GetStringZero(index int, fn func(string)) {
|
||||
pt, err := p.decryptAt(index)
|
||||
if err != nil {
|
||||
fn("")
|
||||
return
|
||||
}
|
||||
|
||||
buf := make([]byte, len(pt))
|
||||
copy(buf, pt)
|
||||
s := string(buf)
|
||||
|
||||
for i := range pt {
|
||||
pt[i] = 0
|
||||
}
|
||||
|
||||
fn(s)
|
||||
|
||||
for i := range buf {
|
||||
buf[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func EncryptEntry(plaintext string, key [32]byte, iv [16]byte) Entry {
|
||||
block, err := aes.NewCipher(key[:])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("strenc: aes.NewCipher: %v", err))
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("strenc: cipher.NewGCM: %v", err))
|
||||
}
|
||||
|
||||
nonceSize := gcm.NonceSize()
|
||||
nonce := make([]byte, nonceSize)
|
||||
copy(nonce, iv[:nonceSize])
|
||||
|
||||
ct := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
|
||||
|
||||
return Entry{
|
||||
Ciphertext: ct,
|
||||
IV: iv,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package stringcrypt
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type EncryptedString struct {
|
||||
ciphertext []byte
|
||||
key []byte
|
||||
}
|
||||
|
||||
func Encrypt(plaintext string) EncryptedString {
|
||||
data := []byte(plaintext)
|
||||
key := make([]byte, len(data))
|
||||
if _, err := rand.Read(key); err != nil {
|
||||
panic(fmt.Sprintf("stringcrypt: rand.Read failed: %v", err))
|
||||
}
|
||||
|
||||
ciphertext := make([]byte, len(data))
|
||||
for i := range data {
|
||||
ciphertext[i] = data[i] ^ key[i]
|
||||
}
|
||||
|
||||
return EncryptedString{ciphertext: ciphertext, key: key}
|
||||
}
|
||||
|
||||
func (e EncryptedString) Decrypt() string {
|
||||
plaintext := make([]byte, len(e.ciphertext))
|
||||
for i := range e.ciphertext {
|
||||
plaintext[i] = e.ciphertext[i] ^ e.key[i]
|
||||
}
|
||||
return string(plaintext)
|
||||
}
|
||||
|
||||
func (e EncryptedString) CiphertextB64() string {
|
||||
return base64.StdEncoding.EncodeToString(e.ciphertext)
|
||||
}
|
||||
|
||||
func (e EncryptedString) KeyB64() string {
|
||||
return base64.StdEncoding.EncodeToString(e.key)
|
||||
}
|
||||
|
||||
func GenerateRuntimeDecryptCall(ctB64, keyB64 string) string {
|
||||
return fmt.Sprintf(
|
||||
`(func() string {
|
||||
_ct, _ := base64.StdEncoding.DecodeString(%q)
|
||||
_ky, _ := base64.StdEncoding.DecodeString(%q)
|
||||
_pt := make([]byte, len(_ct))
|
||||
for _i := range _ct {
|
||||
_pt[_i] = _ct[_i] ^ _ky[_i]
|
||||
}
|
||||
return string(_pt)
|
||||
})()`, ctB64, keyB64)
|
||||
}
|
||||
|
||||
func RandomIdent(prefix string, length int) string {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
b := make([]byte, length)
|
||||
for i := range b {
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("stringcrypt: rand.Int failed: %v", err))
|
||||
}
|
||||
b[i] = charset[n.Int64()]
|
||||
}
|
||||
return prefix + string(b)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package syscallobf
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
dispatchKey uint64
|
||||
dispatchOnce sync.Once
|
||||
)
|
||||
|
||||
func initDispatchKey() {
|
||||
dispatchOnce.Do(func() {
|
||||
var buf [8]byte
|
||||
rand.Read(buf[:])
|
||||
dispatchKey = binary.LittleEndian.Uint64(buf[:])
|
||||
if dispatchKey == 0 {
|
||||
dispatchKey = 0xDEADBEEFCAFEBABE
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func EncodeTrap(num uintptr) uintptr {
|
||||
initDispatchKey()
|
||||
return uintptr(uint64(num) ^ dispatchKey)
|
||||
}
|
||||
|
||||
func DecodeTrap(encoded uintptr) uintptr {
|
||||
return uintptr(uint64(encoded) ^ dispatchKey)
|
||||
}
|
||||
|
||||
type encodedSyscall struct {
|
||||
encoded uintptr
|
||||
}
|
||||
|
||||
func NewEncodedSyscall(num uintptr) encodedSyscall {
|
||||
return encodedSyscall{encoded: EncodeTrap(num)}
|
||||
}
|
||||
|
||||
func (e encodedSyscall) Trap() uintptr {
|
||||
return DecodeTrap(e.encoded)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package syscallobf
|
||||
|
||||
import "syscall"
|
||||
|
||||
func ObfPtrace(request int, pid uintptr, addr uintptr, data uintptr) (uintptr, uintptr, syscall.Errno) {
|
||||
return 0, 0, syscall.ENOSYS
|
||||
}
|
||||
|
||||
func ObfGetpid() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func ObfKill(pid int, sig int) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
func ObfOpen(path string, flags int, mode int) (int, error) {
|
||||
return -1, syscall.ENOSYS
|
||||
}
|
||||
|
||||
func ObfClose(fd int) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
func ObfRead(fd int, buf []byte) (int, error) {
|
||||
return 0, syscall.ENOSYS
|
||||
}
|
||||
|
||||
func ObfMmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset uintptr) (uintptr, error) {
|
||||
return 0, syscall.ENOSYS
|
||||
}
|
||||
|
||||
func ObfMprotect(addr uintptr, length uintptr, prot int) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
func ObfLseek(fd int, offset int64, whence int) (int64, error) {
|
||||
return 0, syscall.ENOSYS
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package syscallobf
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
encPtraceTrap = NewEncodedSyscall(101)
|
||||
encGetpidTrap = NewEncodedSyscall(39)
|
||||
encKillTrap = NewEncodedSyscall(62)
|
||||
encOpenTrap = NewEncodedSyscall(2)
|
||||
encCloseTrap = NewEncodedSyscall(3)
|
||||
encReadTrap = NewEncodedSyscall(0)
|
||||
encMmapTrap = NewEncodedSyscall(9)
|
||||
encMprotectTrap = NewEncodedSyscall(10)
|
||||
encLseekTrap = NewEncodedSyscall(8)
|
||||
)
|
||||
|
||||
func ObfPtrace(request int, pid uintptr, addr uintptr, data uintptr) (uintptr, uintptr, syscall.Errno) {
|
||||
trap := encPtraceTrap.Trap()
|
||||
return syscall.RawSyscall6(trap, uintptr(request), pid, addr, data, 0, 0)
|
||||
}
|
||||
|
||||
func ObfGetpid() int {
|
||||
trap := encGetpidTrap.Trap()
|
||||
r1, _, _ := syscall.RawSyscall(trap, 0, 0, 0)
|
||||
return int(r1)
|
||||
}
|
||||
|
||||
func ObfKill(pid int, sig int) error {
|
||||
trap := encKillTrap.Trap()
|
||||
_, _, errno := syscall.RawSyscall(trap, uintptr(pid), uintptr(sig), 0)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ObfOpen(path string, flags int, mode int) (int, error) {
|
||||
trap := encOpenTrap.Trap()
|
||||
pathBytes, err := syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
r1, _, errno := syscall.RawSyscall(trap, uintptr(unsafe.Pointer(pathBytes)), uintptr(flags), uintptr(mode))
|
||||
if errno != 0 {
|
||||
return int(r1), errno
|
||||
}
|
||||
return int(r1), nil
|
||||
}
|
||||
|
||||
func ObfClose(fd int) error {
|
||||
trap := encCloseTrap.Trap()
|
||||
_, _, errno := syscall.RawSyscall(trap, uintptr(fd), 0, 0)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ObfRead(fd int, buf []byte) (int, error) {
|
||||
trap := encReadTrap.Trap()
|
||||
var p *byte
|
||||
if len(buf) > 0 {
|
||||
p = &buf[0]
|
||||
}
|
||||
r1, _, errno := syscall.RawSyscall(trap, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(len(buf)))
|
||||
if errno != 0 {
|
||||
return int(r1), errno
|
||||
}
|
||||
return int(r1), nil
|
||||
}
|
||||
|
||||
func ObfMmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset uintptr) (uintptr, error) {
|
||||
trap := encMmapTrap.Trap()
|
||||
r1, _, errno := syscall.Syscall6(trap, addr, length, uintptr(prot), uintptr(flags), uintptr(fd), offset)
|
||||
if errno != 0 {
|
||||
return 0, errno
|
||||
}
|
||||
return r1, nil
|
||||
}
|
||||
|
||||
func ObfMprotect(addr uintptr, length uintptr, prot int) error {
|
||||
trap := encMprotectTrap.Trap()
|
||||
_, _, errno := syscall.RawSyscall(trap, addr, length, uintptr(prot))
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ObfLseek(fd int, offset int64, whence int) (int64, error) {
|
||||
trap := encLseekTrap.Trap()
|
||||
r1, _, errno := syscall.RawSyscall(trap, uintptr(fd), uintptr(offset), uintptr(whence))
|
||||
if errno != 0 {
|
||||
return int64(r1), errno
|
||||
}
|
||||
return int64(r1), nil
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package syscallobf
|
||||
|
||||
// ObfuscatedCall wraps a syscall with obfuscated number
|
||||
type ObfuscatedCall struct {
|
||||
encodedNum [4]byte
|
||||
key [4]byte
|
||||
}
|
||||
|
||||
func (o *ObfuscatedCall) Num() uintptr {
|
||||
return uintptr(o.encodedNum[0]^o.key[0]) |
|
||||
uintptr(o.encodedNum[1]^o.key[1])<<8 |
|
||||
uintptr(o.encodedNum[2]^o.key[2])<<16 |
|
||||
uintptr(o.encodedNum[3]^o.key[3])<<24
|
||||
}
|
||||
|
||||
func encode(num uintptr, key [4]byte) ObfuscatedCall {
|
||||
return ObfuscatedCall{
|
||||
encodedNum: [4]byte{
|
||||
byte(num&0xff) ^ key[0],
|
||||
byte((num>>8)&0xff) ^ key[1],
|
||||
byte((num>>16)&0xff) ^ key[2],
|
||||
byte((num>>24)&0xff) ^ key[3],
|
||||
},
|
||||
key: key,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//go:build darwin
|
||||
|
||||
package syscallobf
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func OpenFile(path string, flags int, mode uint32) (int, error) {
|
||||
return syscall.Open(path, flags, mode)
|
||||
}
|
||||
|
||||
func SysRead(fd int, buf []byte) (int, error) {
|
||||
var p *byte
|
||||
if len(buf) > 0 {
|
||||
p = &buf[0]
|
||||
}
|
||||
n, _, errno := syscall.Syscall(
|
||||
uintptr(syscall.SYS_READ),
|
||||
uintptr(fd),
|
||||
uintptr(unsafe.Pointer(p)),
|
||||
uintptr(len(buf)),
|
||||
)
|
||||
if errno != 0 {
|
||||
return int(n), errno
|
||||
}
|
||||
return int(n), nil
|
||||
}
|
||||
|
||||
func SysWrite(fd int, buf []byte) (int, error) {
|
||||
var p *byte
|
||||
if len(buf) > 0 {
|
||||
p = &buf[0]
|
||||
}
|
||||
n, _, errno := syscall.Syscall(
|
||||
uintptr(syscall.SYS_WRITE),
|
||||
uintptr(fd),
|
||||
uintptr(unsafe.Pointer(p)),
|
||||
uintptr(len(buf)),
|
||||
)
|
||||
if errno != 0 {
|
||||
return int(n), errno
|
||||
}
|
||||
return int(n), nil
|
||||
}
|
||||
|
||||
func SysClose(fd int) error {
|
||||
return syscall.Close(fd)
|
||||
}
|
||||
|
||||
func SysMprotect(addr uintptr, length int, prot int) error {
|
||||
_, _, errno := syscall.Syscall(
|
||||
uintptr(syscall.SYS_MPROTECT),
|
||||
addr,
|
||||
uintptr(length),
|
||||
uintptr(prot),
|
||||
)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
//go:build linux
|
||||
|
||||
package syscallobf
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var key [4]byte
|
||||
|
||||
var (
|
||||
obfOpenat ObfuscatedCall
|
||||
obfRead ObfuscatedCall
|
||||
obfWrite ObfuscatedCall
|
||||
obfClose ObfuscatedCall
|
||||
obfMprotect ObfuscatedCall
|
||||
)
|
||||
|
||||
func init() {
|
||||
if _, err := rand.Read(key[:]); err != nil {
|
||||
panic("syscallobf: failed to generate random key: " + err.Error())
|
||||
}
|
||||
|
||||
if runtime.GOARCH == "arm64" {
|
||||
obfOpenat = encode(56, key)
|
||||
obfRead = encode(63, key)
|
||||
obfWrite = encode(64, key)
|
||||
obfClose = encode(57, key)
|
||||
obfMprotect = encode(226, key)
|
||||
} else {
|
||||
obfOpenat = encode(257, key)
|
||||
obfRead = encode(0, key)
|
||||
obfWrite = encode(1, key)
|
||||
obfClose = encode(3, key)
|
||||
obfMprotect = encode(10, key)
|
||||
}
|
||||
}
|
||||
|
||||
//go:nosplit
|
||||
func OpenFile(path string, flags int, mode uint32) (int, error) {
|
||||
p, err := syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r1, _, errno := syscall.Syscall(
|
||||
obfOpenat.Num(),
|
||||
uintptr(syscall.AT_FDCWD),
|
||||
uintptr(unsafe.Pointer(p)),
|
||||
uintptr(flags)|uintptr(mode),
|
||||
)
|
||||
if errno != 0 {
|
||||
return int(r1), errno
|
||||
}
|
||||
return int(r1), nil
|
||||
}
|
||||
|
||||
//go:nosplit
|
||||
func SysRead(fd int, buf []byte) (int, error) {
|
||||
var p *byte
|
||||
if len(buf) > 0 {
|
||||
p = &buf[0]
|
||||
}
|
||||
r1, _, errno := syscall.Syscall(
|
||||
obfRead.Num(),
|
||||
uintptr(fd),
|
||||
uintptr(unsafe.Pointer(p)),
|
||||
uintptr(len(buf)),
|
||||
)
|
||||
if errno != 0 {
|
||||
return int(r1), errno
|
||||
}
|
||||
return int(r1), nil
|
||||
}
|
||||
|
||||
//go:nosplit
|
||||
func SysWrite(fd int, buf []byte) (int, error) {
|
||||
var p *byte
|
||||
if len(buf) > 0 {
|
||||
p = &buf[0]
|
||||
}
|
||||
r1, _, errno := syscall.Syscall(
|
||||
obfWrite.Num(),
|
||||
uintptr(fd),
|
||||
uintptr(unsafe.Pointer(p)),
|
||||
uintptr(len(buf)),
|
||||
)
|
||||
if errno != 0 {
|
||||
return int(r1), errno
|
||||
}
|
||||
return int(r1), nil
|
||||
}
|
||||
|
||||
//go:nosplit
|
||||
func SysClose(fd int) error {
|
||||
_, _, errno := syscall.Syscall(
|
||||
obfClose.Num(),
|
||||
uintptr(fd),
|
||||
0,
|
||||
0,
|
||||
)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:nosplit
|
||||
func SysMprotect(addr uintptr, length int, prot int) error {
|
||||
_, _, errno := syscall.Syscall(
|
||||
obfMprotect.Num(),
|
||||
addr,
|
||||
uintptr(length),
|
||||
uintptr(prot),
|
||||
)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package syscallobf
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEncodeDecodeTrap(t *testing.T) {
|
||||
initDispatchKey()
|
||||
|
||||
tests := []uintptr{0, 1, 39, 62, 101, 0xDEAD, 0xFFFFFFFF}
|
||||
for _, num := range tests {
|
||||
encoded := EncodeTrap(num)
|
||||
if encoded == num {
|
||||
t.Errorf("EncodeTrap(%d) should not equal input", num)
|
||||
}
|
||||
decoded := DecodeTrap(encoded)
|
||||
if decoded != num {
|
||||
t.Errorf("DecodeTrap(EncodeTrap(%d)) = %d, want %d", num, decoded, num)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodedSyscall(t *testing.T) {
|
||||
initDispatchKey()
|
||||
|
||||
enc := NewEncodedSyscall(101)
|
||||
if enc.Trap() != 101 {
|
||||
t.Errorf("EncodedSyscall.Trap() = %d, want 101", enc.Trap())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDispatchKeyNonZero(t *testing.T) {
|
||||
initDispatchKey()
|
||||
if dispatchKey == 0 {
|
||||
t.Error("dispatchKey should not be 0")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Instruction struct {
|
||||
Op Opcode
|
||||
Args []int64
|
||||
}
|
||||
|
||||
type LabelledInstr struct {
|
||||
Label string
|
||||
Instr Instruction
|
||||
Target string
|
||||
}
|
||||
|
||||
func Compile(program []LabelledInstr) []byte {
|
||||
labelAddrs := make(map[string]int)
|
||||
|
||||
type fixup struct {
|
||||
offset int
|
||||
op Opcode
|
||||
target string
|
||||
}
|
||||
var fixups []fixup
|
||||
var bytecode []byte
|
||||
|
||||
for _, entry := range program {
|
||||
if entry.Label != "" {
|
||||
labelAddrs[entry.Label] = len(bytecode)
|
||||
}
|
||||
|
||||
instr := entry.Instr
|
||||
isJump := instr.Op == OpJmp || instr.Op == OpJz || instr.Op == OpJnz
|
||||
|
||||
if isJump && entry.Target != "" {
|
||||
fixups = append(fixups, fixup{
|
||||
offset: len(bytecode),
|
||||
op: instr.Op,
|
||||
target: entry.Target,
|
||||
})
|
||||
ph := make([]byte, InstrSize(instr.Op))
|
||||
ph[0] = encodeOp(instr.Op)
|
||||
bytecode = append(bytecode, ph...)
|
||||
continue
|
||||
}
|
||||
|
||||
arg := int64(0)
|
||||
if len(instr.Args) > 0 {
|
||||
arg = instr.Args[0]
|
||||
}
|
||||
bytecode = append(bytecode, EncodeInstr(instr.Op, arg)...)
|
||||
}
|
||||
|
||||
for _, f := range fixups {
|
||||
addr, ok := labelAddrs[f.target]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("vm: undefined label %q", f.target))
|
||||
}
|
||||
enc := EncodeInstr(f.op, int64(addr))
|
||||
copy(bytecode[f.offset:], enc)
|
||||
}
|
||||
|
||||
return bytecode
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Opcode byte
|
||||
|
||||
const (
|
||||
OpNop Opcode = 0x00
|
||||
OpPush Opcode = 0x01
|
||||
OpPop Opcode = 0x02
|
||||
OpDup Opcode = 0x03
|
||||
OpAdd Opcode = 0x04
|
||||
OpSub Opcode = 0x05
|
||||
OpMul Opcode = 0x06
|
||||
OpDiv Opcode = 0x07
|
||||
OpMod Opcode = 0x08
|
||||
OpXor Opcode = 0x09
|
||||
OpAnd Opcode = 0x0A
|
||||
OpOr Opcode = 0x0B
|
||||
OpNot Opcode = 0x0C
|
||||
OpShl Opcode = 0x0D
|
||||
OpShr Opcode = 0x0E
|
||||
OpCmpEq Opcode = 0x0F
|
||||
OpCmpLt Opcode = 0x10
|
||||
OpCmpGt Opcode = 0x11
|
||||
OpJmp Opcode = 0x12
|
||||
OpJz Opcode = 0x13
|
||||
OpJnz Opcode = 0x14
|
||||
OpLoad Opcode = 0x15
|
||||
OpStore Opcode = 0x16
|
||||
OpCall Opcode = 0x17
|
||||
OpRet Opcode = 0x18
|
||||
OpHalt Opcode = 0x19
|
||||
OpSwap Opcode = 0x1A
|
||||
OpRot Opcode = 0x1B
|
||||
opJunkA Opcode = 0x1C
|
||||
opJunkB Opcode = 0x1D
|
||||
opJunkC Opcode = 0x1E
|
||||
opJunkD Opcode = 0x1F
|
||||
)
|
||||
|
||||
const dispatchKey byte = 0xA5
|
||||
|
||||
var opcodeNames = map[Opcode]string{
|
||||
OpNop: "NOP", OpPush: "PUSH", OpPop: "POP", OpDup: "DUP",
|
||||
OpAdd: "ADD", OpSub: "SUB", OpMul: "MUL", OpDiv: "DIV",
|
||||
OpMod: "MOD", OpXor: "XOR", OpAnd: "AND", OpOr: "OR",
|
||||
OpNot: "NOT", OpShl: "SHL", OpShr: "SHR", OpCmpEq: "CMP_EQ",
|
||||
OpCmpLt: "CMP_LT", OpCmpGt: "CMP_GT", OpJmp: "JMP", OpJz: "JZ",
|
||||
OpJnz: "JNZ", OpLoad: "LOAD", OpStore: "STORE", OpCall: "CALL",
|
||||
OpRet: "RET", OpHalt: "HALT", OpSwap: "SWAP", OpRot: "ROT",
|
||||
}
|
||||
|
||||
func (o Opcode) String() string {
|
||||
if name, ok := opcodeNames[o]; ok {
|
||||
return name
|
||||
}
|
||||
return fmt.Sprintf("UNK(0x%02X)", byte(o))
|
||||
}
|
||||
|
||||
func encodeOp(op Opcode) byte {
|
||||
return byte(op) ^ dispatchKey
|
||||
}
|
||||
|
||||
func decodeOp(b byte) Opcode {
|
||||
return Opcode(b ^ dispatchKey)
|
||||
}
|
||||
|
||||
type argEncoding int
|
||||
|
||||
const (
|
||||
argNone argEncoding = iota
|
||||
argI8
|
||||
argI16
|
||||
argI32
|
||||
argI64
|
||||
)
|
||||
|
||||
func opArgEnc(op Opcode) argEncoding {
|
||||
switch op {
|
||||
case OpPush:
|
||||
return argI64
|
||||
case OpShl, OpShr, OpSwap:
|
||||
return argI8
|
||||
case OpJmp, OpJz, OpJnz:
|
||||
return argI32
|
||||
case OpLoad, OpStore, OpCall:
|
||||
return argI16
|
||||
default:
|
||||
return argNone
|
||||
}
|
||||
}
|
||||
|
||||
func InstrSize(op Opcode) int {
|
||||
switch opArgEnc(op) {
|
||||
case argI64:
|
||||
return 9
|
||||
case argI32:
|
||||
return 5
|
||||
case argI16:
|
||||
return 3
|
||||
case argI8:
|
||||
return 2
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func EncodeInstr(op Opcode, arg int64) []byte {
|
||||
size := InstrSize(op)
|
||||
buf := make([]byte, size)
|
||||
buf[0] = encodeOp(op)
|
||||
|
||||
switch opArgEnc(op) {
|
||||
case argI64:
|
||||
binary.LittleEndian.PutUint64(buf[1:], uint64(arg))
|
||||
case argI32:
|
||||
binary.LittleEndian.PutUint32(buf[1:], uint32(arg))
|
||||
case argI16:
|
||||
binary.LittleEndian.PutUint16(buf[1:], uint16(arg))
|
||||
case argI8:
|
||||
buf[1] = byte(arg)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func DecodeInstr(code []byte, pc int) (Opcode, int64, int) {
|
||||
op := decodeOp(code[pc])
|
||||
size := InstrSize(op)
|
||||
|
||||
var arg int64
|
||||
switch opArgEnc(op) {
|
||||
case argI64:
|
||||
arg = int64(binary.LittleEndian.Uint64(code[pc+1 : pc+9]))
|
||||
case argI32:
|
||||
arg = int64(binary.LittleEndian.Uint32(code[pc+1 : pc+5]))
|
||||
case argI16:
|
||||
arg = int64(binary.LittleEndian.Uint16(code[pc+1 : pc+3]))
|
||||
case argI8:
|
||||
arg = int64(code[pc+1])
|
||||
}
|
||||
return op, arg, size
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
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 }
|
||||
@@ -0,0 +1,537 @@
|
||||
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 := Compile(program)
|
||||
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}},
|
||||
}
|
||||
|
||||
code1 := Compile(program)
|
||||
code2 := Compile(program)
|
||||
|
||||
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 := Compile(program)
|
||||
|
||||
// First byte should be OpPush ^ dispatchKey, not OpPush itself
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user