Files
go-obfuscation/internal/transform/transform.go
T
2026-05-31 11:23:07 +01:00

1610 lines
40 KiB
Go

package transform
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
cryptoRand "crypto/rand"
"crypto/sha256"
"fmt"
"go/ast"
"go/parser"
"go/token"
"math/rand"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/self-evolving-research/obfuscator/pkg/strenc"
"github.com/self-evolving-research/obfuscator/pkg/stringcrypt"
"github.com/self-evolving-research/obfuscator/pkg/vm"
)
type Config struct {
EncryptStrings bool
InjectOpaquePreds bool
InjectDeadCode bool
RandomizeOrder bool
IndirectDispatch bool
BogusControlFlow bool
FlattenControl bool
SpliceCode bool
BuildSeed int64
InjectBuildID bool
VirtualizeFunctions bool
AntiDisassembly bool
JunkStrings bool
}
func DefaultConfig() Config {
return Config{
EncryptStrings: true,
InjectOpaquePreds: true,
InjectDeadCode: true,
RandomizeOrder: true,
IndirectDispatch: true,
BogusControlFlow: true,
FlattenControl: true,
}
}
type Transformer struct {
config Config
rng *rand.Rand
}
func New(config Config) *Transformer {
return &Transformer{
config: config,
rng: rand.New(rand.NewSource(config.BuildSeed)),
}
}
type stringReplacement struct {
start int
end int
replExpr string
}
func (t *Transformer) TransformFile(srcPath, dstPath string) error {
src, err := os.ReadFile(srcPath)
if err != nil {
return fmt.Errorf("read %s: %w", srcPath, err)
}
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, srcPath, src, parser.ParseComments)
if err != nil {
return fmt.Errorf("parse %s: %w", srcPath, err)
}
result := string(src)
if t.config.EncryptStrings {
result, err = t.transformStrings(fset, f, result)
if err != nil {
return fmt.Errorf("encrypt strings %s: %w", srcPath, err)
}
result = convertConstToVar(result)
}
if t.config.InjectOpaquePreds {
result = t.injectOpaquePredicates(result)
}
if t.config.InjectDeadCode {
result = t.injectDeadCode(result)
}
if t.config.BogusControlFlow {
result = t.injectBogusControlFlow(result)
}
if t.config.IndirectDispatch {
result = t.indirectDispatch(result)
}
if t.config.SpliceCode {
result = t.spliceCode(result)
}
if t.config.FlattenControl {
result = t.flattenSimpleFunctions(result)
}
if t.config.InjectBuildID && strings.HasSuffix(srcPath, "main.go") {
result = t.injectBuildID(result)
}
if t.config.VirtualizeFunctions {
result = t.virtualizeFunctions(result)
}
if t.config.AntiDisassembly {
result = t.injectAntiDisassembly(result)
}
if t.config.JunkStrings {
result = t.injectJunkStrings(result)
}
if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
return fmt.Errorf("mkdir %s: %w", filepath.Dir(dstPath), err)
}
if err := os.WriteFile(dstPath, []byte(result), 0644); err != nil {
return fmt.Errorf("write %s: %w", dstPath, err)
}
return nil
}
func (t *Transformer) TransformDir(srcDir, dstDir string) error {
entries, err := os.ReadDir(srcDir)
if err != nil {
return fmt.Errorf("readdir %s: %w", srcDir, err)
}
for _, entry := range entries {
srcPath := filepath.Join(srcDir, entry.Name())
dstPath := filepath.Join(dstDir, entry.Name())
if entry.IsDir() {
if err := t.TransformDir(srcPath, dstPath); err != nil {
return err
}
continue
}
if !strings.HasSuffix(entry.Name(), ".go") {
data, err := os.ReadFile(srcPath)
if err != nil {
return err
}
if err := os.MkdirAll(dstDir, 0755); err != nil {
return err
}
if err := os.WriteFile(dstPath, data, 0644); err != nil {
return err
}
continue
}
if strings.HasSuffix(entry.Name(), "_test.go") {
continue
}
fmt.Printf(" transforming: %s\n", entry.Name())
if err := t.TransformFile(srcPath, dstPath); err != nil {
return fmt.Errorf("transform %s: %w", srcPath, err)
}
}
return nil
}
func (t *Transformer) transformStrings(fset *token.FileSet, f *ast.File, src string) (string, error) {
type repl struct {
start int
end int
expr string
}
importRanges := map[int]bool{}
ast.Inspect(f, func(n ast.Node) bool {
imp, ok := n.(*ast.ImportSpec)
if !ok {
return true
}
if imp.Path != nil {
start := fset.Position(imp.Path.Pos()).Offset
end := fset.Position(imp.Path.End()).Offset
for i := start; i < end; i++ {
importRanges[i] = true
}
}
return true
})
var replacements []repl
var plaintexts []string
ast.Inspect(f, func(n ast.Node) bool {
basicLit, ok := n.(*ast.BasicLit)
if !ok || basicLit.Kind != token.STRING {
return true
}
startOff := fset.Position(basicLit.Pos()).Offset
if importRanges[startOff] {
return true
}
raw := basicLit.Value
if len(raw) < 4 {
return true
}
var unquoted string
if raw[0] == '"' && raw[len(raw)-1] == '"' {
var err error
unquoted, err = unquoteGoString(raw)
if err != nil || len(unquoted) < 2 {
return true
}
} else {
return true
}
if shouldSkipString(unquoted) {
return true
}
idx := len(plaintexts)
plaintexts = append(plaintexts, unquoted)
pos := fset.Position(basicLit.Pos())
end := fset.Position(basicLit.End())
replacements = append(replacements, repl{
start: pos.Offset,
end: end.Offset,
expr: fmt.Sprintf("_decStr(%d)", idx),
})
return true
})
if len(replacements) == 0 {
return src, nil
}
for i := len(replacements) - 1; i >= 0; i-- {
r := replacements[i]
src = src[:r.start] + r.expr + src[r.end:]
}
var seed [32]byte
if _, err := cryptoRand.Read(seed[:]); err != nil {
return "", fmt.Errorf("strenc: generate seed: %w", err)
}
masterKey := strenc.DeriveMasterKey(seed)
var entries []strenc.Entry
const padBlock = 64
for _, pt := range plaintexts {
ptBytes := []byte(pt)
ptLen := len(ptBytes)
padded := make([]byte, 2+ptLen)
padded[0] = byte(ptLen & 0xFF)
padded[1] = byte((ptLen >> 8) & 0xFF)
copy(padded[2:], ptBytes)
if len(padded)%padBlock != 0 {
targetLen := ((len(padded) / padBlock) + 1) * padBlock
paddedBuf := make([]byte, targetLen)
copy(paddedBuf, padded)
cryptoRand.Read(paddedBuf[len(padded):])
padded = paddedBuf
}
var iv [16]byte
if _, err := cryptoRand.Read(iv[:]); err != nil {
return "", fmt.Errorf("strenc: generate iv: %w", err)
}
mac := hmac.New(sha256.New, masterKey[:])
mac.Write(iv[:])
var subkey [32]byte
copy(subkey[:], mac.Sum(nil))
entry := encryptEntryGCM(string(padded), subkey, iv)
entries = append(entries, entry)
}
var poolCode strings.Builder
poolCode.WriteString("\nvar _masterSeed = [32]byte{")
for i, b := range seed {
if i > 0 {
poolCode.WriteString(", ")
}
fmt.Fprintf(&poolCode, "0x%02X", b)
}
poolCode.WriteString("}\n")
poolCode.WriteString("var _encEntries = []strenc.Entry{\n")
for _, e := range entries {
poolCode.WriteString("\t{Ciphertext: []byte{")
for i, b := range e.Ciphertext {
if i > 0 {
poolCode.WriteString(", ")
}
fmt.Fprintf(&poolCode, "0x%02X", b)
}
poolCode.WriteString("}, IV: [16]byte{")
for i, b := range e.IV {
if i > 0 {
poolCode.WriteString(", ")
}
fmt.Fprintf(&poolCode, "0x%02X", b)
}
poolCode.WriteString("}},\n")
}
poolCode.WriteString("}\n")
poolCode.WriteString("var _encPool = strenc.NewPool(_masterSeed, _encEntries)\n")
poolCode.WriteString("//go:noinline\nfunc _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]) }\n")
insertAt := findImportBlockEnd(src)
if insertAt == -1 {
pkgIdx := strings.Index(src, "package ")
if pkgIdx != -1 {
lineEnd := strings.Index(src[pkgIdx:], "\n")
if lineEnd != -1 {
insertAt = pkgIdx + lineEnd + 1
}
}
}
if insertAt != -1 && insertAt < len(src) {
src = src[:insertAt] + poolCode.String() + src[insertAt:]
}
src = t.ensureImport(src, "github.com/self-evolving-research/obfuscator/pkg/strenc")
return src, nil
}
func encryptEntryGCM(plaintext string, key [32]byte, iv [16]byte) strenc.Entry {
block, err := aes.NewCipher(key[:])
if err != nil {
panic(fmt.Sprintf("transform: aes.NewCipher: %v", err))
}
gcm, err := cipher.NewGCM(block)
if err != nil {
panic(fmt.Sprintf("transform: cipher.NewGCM: %v", err))
}
nonceSize := gcm.NonceSize()
nonce := make([]byte, nonceSize)
copy(nonce, iv[:nonceSize])
ct := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
return strenc.Entry{Ciphertext: ct, IV: iv}
}
func writeHexLiteral(sb *strings.Builder, data []byte) {
sb.WriteString("[]byte{")
for i, b := range data {
if i > 0 {
sb.WriteString(", ")
}
fmt.Fprintf(sb, "0x%02X", b)
}
sb.WriteString("}")
}
func findImportBlockEnd(src string) int {
idx := strings.Index(src, "import (")
if idx != -1 {
rest := src[idx+len("import ("):]
depth := 1
for i, c := range rest {
if c == '(' {
depth++
} else if c == ')' {
depth--
if depth == 0 {
return idx + len("import (") + i + 1
}
}
}
return -1
}
idx = strings.Index(src, "import \"")
if idx != -1 {
lineEnd := strings.Index(src[idx:], "\n")
if lineEnd != -1 {
return idx + lineEnd + 1
}
return len(src)
}
return -1
}
func shouldSkipString(s string) bool {
skipPrefixes := []string{
"go:", "//", "+build", "package", "go:", "module",
"//go:", "// +build",
}
for _, p := range skipPrefixes {
if strings.HasPrefix(s, p) {
return true
}
}
if len(s) == 0 {
return true
}
if s[0] == '/' || s[0] == '+' {
return true
}
return false
}
func unquoteGoString(s string) (string, error) {
if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' {
return "", fmt.Errorf("not a quoted string")
}
s = s[1 : len(s)-1]
var result strings.Builder
for i := 0; i < len(s); i++ {
if s[i] == '\\' && i+1 < len(s) {
switch s[i+1] {
case 'n':
result.WriteByte('\n')
i++
case 't':
result.WriteByte('\t')
i++
case 'r':
result.WriteByte('\r')
i++
case '\\':
result.WriteByte('\\')
i++
case '"':
result.WriteByte('"')
i++
default:
result.WriteByte(s[i])
}
} else {
result.WriteByte(s[i])
}
}
return result.String(), nil
}
func (t *Transformer) injectOpaquePredicates(src string) string {
lines := strings.Split(src, "\n")
var result []string
inFuncBody := false
funcStmtCount := 0
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "func ") && strings.HasSuffix(trimmed, "{") {
inFuncBody = true
funcStmtCount = 0
result = append(result, line)
continue
}
if inFuncBody && trimmed == "}" {
inFuncBody = false
result = append(result, line)
continue
}
if inFuncBody && strings.HasPrefix(line, "\t") && !strings.HasPrefix(line, "\t\t") && trimmed != "" {
funcStmtCount++
if funcStmtCount >= 3 && t.rng.Float64() < 0.4 {
result = append(result, "\t"+"if func() bool { return "+t.generateTrueExpression()+" }() { _ = struct{}{} }")
}
}
result = append(result, line)
}
return strings.Join(result, "\n")
}
func (t *Transformer) injectDeadCode(src string) string {
lines := strings.Split(src, "\n")
var result []string
inFuncBody := false
funcStmtCount := 0
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "func ") && strings.HasSuffix(trimmed, "{") {
inFuncBody = true
funcStmtCount = 0
result = append(result, line)
continue
}
if inFuncBody && trimmed == "}" {
inFuncBody = false
result = append(result, line)
continue
}
if inFuncBody && strings.HasPrefix(line, "\t") && !strings.HasPrefix(line, "\t\t") && trimmed != "" {
funcStmtCount++
if funcStmtCount >= 3 && t.rng.Float64() < 0.3 {
result = append(result, "\t"+"if func() bool { return "+t.generateFalseExpression()+" }() { println(\"unreachable\") }")
}
}
result = append(result, line)
}
return strings.Join(result, "\n")
}
func isValidIdent(s string) bool {
if len(s) == 0 {
return false
}
for i, c := range s {
if c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') {
continue
}
if i > 0 && c >= '0' && c <= '9' {
continue
}
return false
}
return true
}
func getIndent(line string) string {
indent := ""
for _, c := range line {
if c == ' ' || c == '\t' {
indent += string(c)
} else {
break
}
}
return indent
}
func convertConstToVar(src string) string {
lines := strings.Split(src, "\n")
inConstBlock := false
hasEncrypted := false
constStartLines := []int{}
for i, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "const (" {
inConstBlock = true
hasEncrypted = false
constStartLines = append(constStartLines, i)
continue
}
if inConstBlock && trimmed == ")" {
if hasEncrypted {
lines[constStartLines[len(constStartLines)-1]] = strings.Replace(
lines[constStartLines[len(constStartLines)-1]], "const (", "var (", 1,
)
}
inConstBlock = false
continue
}
if inConstBlock && (strings.Contains(line, "base64.StdEncoding.DecodeString") || strings.Contains(line, "_decStr(")) {
hasEncrypted = true
}
}
for i, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "const ") && (strings.Contains(line, "base64.StdEncoding.DecodeString") || strings.Contains(line, "_decStr(")) {
lines[i] = strings.Replace(line, "const ", "var ", 1)
}
}
return strings.Join(lines, "\n")
}
func replaceInLines(lines []string, old, new string) []string {
var result []string
for _, l := range lines {
result = append(result, strings.Replace(l, old, new, 1))
}
return result
}
func (t *Transformer) RandomizeOrder(f *ast.File) {
if len(f.Decls) <= 1 {
return
}
t.rng.Shuffle(len(f.Decls), func(i, j int) {
f.Decls[i], f.Decls[j] = f.Decls[j], f.Decls[i]
})
}
func (t *Transformer) injectBogusControlFlow(src string) string {
lines := strings.Split(src, "\n")
var result []string
inFuncBody := false
stmtCount := 0
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "func ") && strings.HasSuffix(trimmed, "{") {
inFuncBody = true
stmtCount = 0
result = append(result, line)
continue
}
if inFuncBody && trimmed == "}" {
inFuncBody = false
result = append(result, line)
continue
}
if inFuncBody && strings.HasPrefix(line, "\t") && !strings.HasPrefix(line, "\t\t") && trimmed != "" {
stmtCount++
if stmtCount >= 2 && t.rng.Float64() < 0.25 {
result = append(result, t.generateBogusBranch())
}
}
result = append(result, line)
}
return strings.Join(result, "\n")
}
func (t *Transformer) generateBogusBranch() string {
n := t.rng.Intn(4)
switch n {
case 0:
v := stringcrypt.RandomIdent("_bc", 5)
return fmt.Sprintf(
"\tif func() bool { %s := int64(1); for i := 0; i < 10; i++ { %s = %s*7%%49999999 }; return %s == 1 }() { _ = struct{}{} }",
v, v, v, v,
)
case 1:
v := stringcrypt.RandomIdent("_bc", 5)
a := t.rng.Intn(100) + 1
return fmt.Sprintf(
"\tif func() bool { %s := %d*%d; return %s%%2 == 0 && %s > 0 }() { _ = struct{}{} }",
v, a, a, v, v,
)
case 2:
v := stringcrypt.RandomIdent("_bc", 5)
return fmt.Sprintf(
"\tif func() bool { %s := [4]byte{0x72, 0x6F, 0x6F, 0x74}; return len(%s) == 4 }() { _ = struct{}{} }",
v, v,
)
default:
v := stringcrypt.RandomIdent("_bc", 5)
return fmt.Sprintf(
"\t{ %s := func() int { n := 0; for i := 1; i <= 100; i++ { n += i }; return n }(); _ = %s }",
v, v,
)
}
}
func (t *Transformer) flattenSimpleFunctions(src string) string {
lines := strings.Split(src, "\n")
var result []string
i := 0
for i < len(lines) {
trimmed := strings.TrimSpace(lines[i])
if strings.HasPrefix(trimmed, "func ") && strings.HasSuffix(trimmed, "{") && !strings.Contains(trimmed, "main") && !strings.Contains(trimmed, "init()") {
funcLines := []string{lines[i]}
i++
depth := 1
for i < len(lines) && depth > 0 {
for _, c := range lines[i] {
if c == '{' {
depth++
}
if c == '}' {
depth--
}
}
funcLines = append(funcLines, lines[i])
i++
}
if len(funcLines) > 5 && t.rng.Float64() < 0.4 {
flattened := flattenFunctionBody(funcLines)
result = append(result, flattened...)
} else {
result = append(result, funcLines...)
}
continue
}
result = append(result, lines[i])
i++
}
return strings.Join(result, "\n")
}
func flattenFunctionBody(funcLines []string) []string {
funcSig := funcLines[0]
bodyLines := funcLines[1 : len(funcLines)-1]
closingBrace := funcLines[len(funcLines)-1]
if len(bodyLines) < 3 {
return funcLines
}
var statements [][]string
var current []string
depth := 0
for _, l := range bodyLines {
trimmed := strings.TrimSpace(l)
if trimmed == "" {
continue
}
for _, c := range l {
if c == '{' {
depth++
}
if c == '}' {
depth--
}
}
current = append(current, l)
if depth <= 0 && trimmed != "{" {
statements = append(statements, current)
current = nil
depth = 0
}
}
if len(current) > 0 {
statements = append(statements, current)
}
if len(statements) < 3 {
return funcLines
}
for _, stmt := range statements {
for _, line := range stmt {
trimmed := strings.TrimSpace(line)
if strings.Contains(trimmed, ":=") {
parts := strings.SplitN(trimmed, ":=", 2)
varName := strings.TrimSpace(parts[0])
if isValidIdent(varName) && !strings.Contains(varName, ",") {
rhs := strings.TrimSpace(parts[1])
if inferTypeFromRHS(rhs) == "" {
return funcLines
}
}
}
}
}
var result []string
result = append(result, funcSig)
var hoistedVars []string
declaredVars := map[string]bool{}
for idx, stmt := range statements {
for _, line := range stmt {
trimmed := strings.TrimSpace(line)
if strings.Contains(trimmed, ":=") {
parts := strings.SplitN(trimmed, ":=", 2)
varName := strings.TrimSpace(parts[0])
if !isValidIdent(varName) && !strings.Contains(varName, ",") {
continue
}
rhs := strings.TrimSpace(parts[1])
if !strings.Contains(varName, ",") {
typ := inferTypeFromRHS(rhs)
if typ == "" {
continue
}
if !declaredVars[varName] {
hoistedVars = append(hoistedVars, "\tvar "+varName+" "+typ)
declaredVars[varName] = true
}
statements[idx] = replaceInLines(statements[idx], varName+" :=", varName+" =")
} else {
names := strings.Split(varName, ",")
allValid := true
for _, n := range names {
n = strings.TrimSpace(n)
if n != "" && !isValidIdent(n) {
allValid = false
break
}
}
if !allValid {
continue
}
for _, n := range names {
n = strings.TrimSpace(n)
if n != "" && !declaredVars[n] {
hoistedVars = append(hoistedVars, "\tvar "+n+" int")
declaredVars[n] = true
}
}
statements[idx] = replaceInLines(statements[idx], varName+" :=", varName+" =")
}
}
}
}
for _, v := range hoistedVars {
result = append(result, v)
}
stateVar := stringcrypt.RandomIdent("_st", 4)
result = append(result, "\t"+stateVar+" := 0")
result = append(result, "\tfor {")
result = append(result, "\t\tswitch "+stateVar+" {")
numStates := len(statements)
for idx, stmt := range statements {
result = append(result, fmt.Sprintf("\t\tcase %d:", idx))
for _, line := range stmt {
result = append(result, "\t\t"+strings.TrimPrefix(line, "\t"))
}
if idx < numStates-1 {
result = append(result, fmt.Sprintf("\t\t\t%s = %d", stateVar, idx+1))
} else {
result = append(result, "\t\t\treturn")
}
}
result = append(result, "\t\tdefault:")
result = append(result, "\t\t\treturn")
result = append(result, "\t\t}")
result = append(result, "\t}")
result = append(result, closingBrace)
return result
}
func (t *Transformer) generateTrueExpression() string {
a := t.rng.Intn(1000) + 1
switch t.rng.Intn(6) {
case 0:
return fmt.Sprintf("%d%%%d == 0", a*7, 7)
case 1:
return fmt.Sprintf("(%d^%d) == 0", a, a)
case 2:
return fmt.Sprintf("^(%d|%d) == ^%d", a, a, a)
case 3:
return fmt.Sprintf("(%d & %d) == %d", a, a, a)
case 4:
return fmt.Sprintf("len([%d]byte{}) == 0", t.rng.Intn(8)+1)
default:
b := t.rng.Intn(100) + 1
return fmt.Sprintf("(%d+%d) > %d", b, a, b+a-1)
}
}
func (t *Transformer) generateFalseExpression() string {
a := t.rng.Intn(0xFFFF) + 1
switch t.rng.Intn(6) {
case 0:
return fmt.Sprintf("(%d - %d) < 0", a, a)
case 1:
return fmt.Sprintf("func() bool { return 1 < 0 }()")
case 2:
return fmt.Sprintf("func() bool { var _x = %d; return _x > _x }()", a)
case 3:
return fmt.Sprintf("func() bool { var _x int = %d; _x -= %d; return _x > 0 }()", a, a)
case 4:
return fmt.Sprintf("len([%d]byte{}) < 0", t.rng.Intn(8)+1)
default:
return fmt.Sprintf("func() bool { return (%d^%d) > 0 && (%d^%d) < 0 }()", a, a, a, a)
}
}
func (t *Transformer) injectBuildID(src string) string {
buildID := fmt.Sprintf("BUILD_%d_%08X", t.config.BuildSeed, t.rng.Int31())
decl := fmt.Sprintf("\nvar _buildID = %q // polymorphic build marker\nvar _ = _buildID\n", buildID)
insertAfter := findImportBlockEnd(src)
if insertAfter == -1 {
pkgIdx := strings.Index(src, "package ")
if pkgIdx == -1 {
return src
}
lineEnd := strings.Index(src[pkgIdx:], "\n")
if lineEnd == -1 {
return src + decl
}
insertAfter = pkgIdx + lineEnd
}
return src[:insertAfter+1] + decl + src[insertAfter+1:]
}
func (t *Transformer) indirectDispatch(src string) string {
lines := strings.Split(src, "\n")
var result []string
var wrappers []string
dispatchIdx := 0
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if !strings.HasPrefix(trimmed, "func ") || !strings.HasSuffix(trimmed, "{") {
result = append(result, line)
continue
}
parenIdx := strings.Index(trimmed, "(")
if parenIdx == -1 {
result = append(result, line)
continue
}
funcName := strings.TrimSpace(trimmed[5:parenIdx])
if funcName == "main" || funcName == "init" || funcName == "" || strings.ToUpper(funcName[:1]) == funcName[:1] || strings.HasPrefix(funcName, "_") {
result = append(result, line)
continue
}
obfSuffix := fmt.Sprintf("_obf%d", t.rng.Intn(9000)+1000)
renameFrom := "func " + funcName + "("
renameTo := "func " + funcName + obfSuffix + "("
renamedLine := strings.Replace(line, renameFrom, renameTo, 1)
result = append(result, renamedLine)
restOfSig := trimmed[parenIdx:]
sigClean := strings.TrimSuffix(restOfSig, " {")
depth := 0
paramsEnd := -1
for i, c := range sigClean {
if c == '(' {
depth++
} else if c == ')' {
depth--
if depth == 0 {
paramsEnd = i
break
}
}
}
if paramsEnd == -1 {
continue
}
params := sigClean[:paramsEnd+1]
returnType := strings.TrimSpace(sigClean[paramsEnd+1:])
hasReturn := returnType != ""
argsStart := strings.Index(params, "(") + 1
argsEnd := strings.LastIndex(params, ")")
argStr := params[argsStart:argsEnd]
argNames := extractArgNames(argStr)
callArgs := strings.Join(argNames, ", ")
ptrVar := fmt.Sprintf("_fptr%d", dispatchIdx)
dispatchIdx++
if hasReturn {
ptrDecl := fmt.Sprintf("var %s = %s", ptrVar, funcName+obfSuffix)
wrappers = append(wrappers, ptrDecl)
wrapperFunc := fmt.Sprintf("//go:noinline\nfunc %s%s %s { return %s(%s) }",
funcName, params, returnType, ptrVar, callArgs)
wrappers = append(wrappers, wrapperFunc)
} else {
ptrDecl := fmt.Sprintf("var %s = %s", ptrVar, funcName+obfSuffix)
wrappers = append(wrappers, ptrDecl)
wrapperFunc := fmt.Sprintf("//go:noinline\nfunc %s%s { %s(%s) }",
funcName, params, ptrVar, callArgs)
wrappers = append(wrappers, wrapperFunc)
}
}
if len(wrappers) > 0 {
insertAt := -1
inImportBlock := false
for i, line := range result {
t := strings.TrimSpace(line)
if strings.HasPrefix(t, "import") && strings.Contains(t, "(") {
inImportBlock = true
continue
}
if inImportBlock && t == ")" {
insertAt = i + 1
break
}
}
if insertAt == -1 {
for i, line := range result {
if strings.HasPrefix(strings.TrimSpace(line), "package ") {
insertAt = i + 1
break
}
}
}
if insertAt >= 0 {
for insertAt < len(result) && strings.TrimSpace(result[insertAt]) == "" {
insertAt++
}
newResult := make([]string, 0, len(result)+len(wrappers)+1)
newResult = append(newResult, result[:insertAt]...)
newResult = append(newResult, "")
newResult = append(newResult, wrappers...)
newResult = append(newResult, result[insertAt:]...)
result = newResult
}
}
return strings.Join(result, "\n")
}
func extractArgNames(argStr string) []string {
if strings.TrimSpace(argStr) == "" {
return nil
}
var names []string
for _, arg := range strings.Split(argStr, ",") {
arg = strings.TrimSpace(arg)
parts := strings.Fields(arg)
if len(parts) >= 2 {
names = append(names, parts[0])
} else if len(parts) == 1 {
names = append(names, parts[0])
}
}
return names
}
func trimFirstParen(s string) string {
if len(s) > 0 && s[0] == '(' {
return s
}
return "(" + s
}
func (t *Transformer) spliceCode(src string) string {
lines := strings.Split(src, "\n")
var result []string
i := 0
for i < len(lines) {
trimmed := strings.TrimSpace(lines[i])
if strings.HasPrefix(trimmed, "func ") && strings.HasSuffix(trimmed, "{") &&
!strings.Contains(trimmed, "main") && !strings.Contains(trimmed, "init") &&
!strings.Contains(trimmed, "func()") {
funcSig := lines[i]
i++
depth := 1
var bodyLines []string
for i < len(lines) && depth > 0 {
for _, c := range lines[i] {
if c == '{' {
depth++
}
if c == '}' {
depth--
}
}
if depth > 0 {
bodyLines = append(bodyLines, lines[i])
}
i++
}
simpleStmts := 0
hasControlFlow := false
for _, bl := range bodyLines {
bt := strings.TrimSpace(bl)
if bt == "" || bt == "{" || bt == "}" {
continue
}
if strings.HasPrefix(bt, "if ") || strings.HasPrefix(bt, "for ") ||
strings.HasPrefix(bt, "switch ") || strings.HasPrefix(bt, "select ") ||
strings.HasPrefix(bt, "case ") || strings.HasPrefix(bt, "default:") {
hasControlFlow = true
break
}
if strings.HasPrefix(bt, "return ") || strings.HasPrefix(bt, "return") {
break
}
simpleStmts++
}
if simpleStmts >= 5 && !hasControlFlow && t.rng.Float64() < 0.5 {
spliced := t.buildSplicedFunction(funcSig, bodyLines)
result = append(result, spliced...)
} else {
result = append(result, funcSig)
result = append(result, bodyLines...)
result = append(result, "}")
}
continue
}
result = append(result, lines[i])
i++
}
return strings.Join(result, "\n")
}
func (t *Transformer) buildSplicedFunction(funcSig string, bodyLines []string) []string {
var result []string
result = append(result, funcSig)
var stmts []string
var hoistedVars []string
declaredVars := map[string]bool{}
for _, line := range bodyLines {
bt := strings.TrimSpace(line)
if bt == "" || bt == "{" || bt == "}" {
continue
}
if strings.Contains(bt, ":=") {
parts := strings.SplitN(bt, ":=", 2)
varName := strings.TrimSpace(parts[0])
if !isValidIdent(varName) && !strings.Contains(varName, ",") {
stmts = append(stmts, bt)
continue
}
rhs := strings.TrimSpace(parts[1])
if !strings.Contains(varName, ",") {
typ := inferTypeFromRHS(rhs)
if typ == "" {
stmts = append(stmts, bt)
continue
}
if !declaredVars[varName] {
hoistedVars = append(hoistedVars, "\tvar "+varName+" "+typ)
declaredVars[varName] = true
}
bt = varName + " = " + rhs
} else {
names := strings.Split(varName, ",")
allValid := true
for _, n := range names {
n = strings.TrimSpace(n)
if n != "" && !isValidIdent(n) {
allValid = false
break
}
}
if !allValid {
stmts = append(stmts, bt)
continue
}
for _, n := range names {
n = strings.TrimSpace(n)
if n != "" && !declaredVars[n] {
hoistedVars = append(hoistedVars, "\tvar "+n+" int")
declaredVars[n] = true
}
}
bt = varName + " = " + rhs
}
}
stmts = append(stmts, bt)
}
for _, v := range hoistedVars {
result = append(result, v)
}
stateVar := stringcrypt.RandomIdent("_sp", 4)
result = append(result, "\t"+stateVar+" := 0")
returnVar := ""
lastStmt := strings.TrimSpace(stmts[len(stmts)-1])
if strings.HasPrefix(lastStmt, "return ") {
returnExpr := strings.TrimPrefix(lastStmt, "return ")
returnVar = stringcrypt.RandomIdent("_ret", 4)
result = append(result, "\tvar "+returnVar+" "+inferTypeFromRHS(returnExpr))
stmts[len(stmts)-1] = returnVar + " = " + returnExpr
}
result = append(result, "\tfor {")
result = append(result, "\t\tswitch "+stateVar+" {")
numFragments := 2
if len(stmts) > 7 {
numFragments = 3
}
fragmentSize := len(stmts) / numFragments
fragIdx := 0
for f := 0; f < numFragments; f++ {
result = append(result, fmt.Sprintf("\t\tcase %d:", f))
end := (f + 1) * fragmentSize
if f == numFragments-1 {
end = len(stmts)
}
for j := f * fragmentSize; j < end; j++ {
result = append(result, "\t\t\t"+stmts[j])
}
if f < numFragments-1 {
result = append(result, fmt.Sprintf("\t\t\t%s = %d", stateVar, f+1))
} else {
if returnVar != "" {
result = append(result, "\t\t\treturn "+returnVar)
} else {
result = append(result, "\t\t\treturn")
}
}
fragIdx++
}
result = append(result, "\t\tdefault:")
if returnVar != "" {
result = append(result, "\t\t\treturn "+returnVar)
} else {
result = append(result, "\t\t\treturn")
}
result = append(result, "\t\t}")
result = append(result, "\t}")
result = append(result, "}")
return result
}
func inferTypeFromRHS(rhs string) string {
rhs = strings.TrimSpace(rhs)
if strings.HasPrefix(rhs, "\"") || strings.HasPrefix(rhs, "`") {
return "string"
}
if strings.HasPrefix(rhs, "fmt.Sprintf") || strings.HasPrefix(rhs, "func() string") {
return "string"
}
if strings.HasPrefix(rhs, "[]byte") || strings.HasPrefix(rhs, "make([]byte") {
return "[]byte"
}
if rhs == "true" || rhs == "false" {
return "bool"
}
if strings.Contains(rhs, "sha256.Sum256") {
return "[32]byte"
}
return ""
}
var vmLenPattern = regexp.MustCompile(`(?m)^func\s+(\w+)\((\w+)\s+string\)\s+bool\s*\{\s*\n\s*return\s+len\((\w+)\)\s*>\s*(\d+)\s*\n\s*\}`)
var vmLenEqPattern = regexp.MustCompile(`(?m)^func\s+(\w+)\((\w+)\s+string\)\s+bool\s*\{\s*\n\s*return\s+len\((\w+)\)\s*==\s*(\d+)\s*\n\s*\}`)
var vmLenLtPattern = regexp.MustCompile(`(?m)^func\s+(\w+)\((\w+)\s+string\)\s+bool\s*\{\s*\n\s*return\s+len\((\w+)\)\s*<\s*(\d+)\s*\n\s*\}`)
var vmLenGePattern = regexp.MustCompile(`(?m)^func\s+(\w+)\((\w+)\s+string\)\s+bool\s*\{\s*\n\s*return\s+len\((\w+)\)\s*>=\s*(\d+)\s*\n\s*\}`)
var vmArith2Pattern = regexp.MustCompile(`(?m)^func\s+(\w+)\((\w+),\s*(\w+)\s+(?:int|int64)\)\s+(?:int|int64)\s*\{\s*\n\s*return\s+(\w+)\s*([+\-*/%^&|])\s*(\w+)\s*\n\s*\}`)
var vmArith1Pattern = regexp.MustCompile(`(?m)^func\s+(\w+)\((\w+)\s+(?:int|int64)\)\s+(?:int|int64)\s*\{\s*\n\s*return\s+(\w+)\s*([+\-*/%^&|])\s*(\d+)\s*\n\s*\}`)
func (t *Transformer) virtualizeFunctions(src string) string {
type replacement struct {
start int
end int
text string
}
var repls []replacement
for _, loc := range vmLenPattern.FindAllStringIndex(src, -1) {
full := src[loc[0]:loc[1]]
sub := vmLenPattern.FindStringSubmatch(full)
if len(sub) >= 5 && sub[2] == sub[3] {
n, _ := strconv.ParseInt(sub[4], 10, 64)
bc := compileLenCmpBytecode("gt", n)
repls = append(repls, replacement{loc[0], loc[1], buildVMBoolFunc(sub[1], sub[2], bc)})
}
}
for _, loc := range vmLenEqPattern.FindAllStringIndex(src, -1) {
full := src[loc[0]:loc[1]]
sub := vmLenEqPattern.FindStringSubmatch(full)
if len(sub) >= 5 && sub[2] == sub[3] {
n, _ := strconv.ParseInt(sub[4], 10, 64)
bc := compileLenCmpBytecode("eq", n)
repls = append(repls, replacement{loc[0], loc[1], buildVMBoolFunc(sub[1], sub[2], bc)})
}
}
for _, loc := range vmLenLtPattern.FindAllStringIndex(src, -1) {
full := src[loc[0]:loc[1]]
sub := vmLenLtPattern.FindStringSubmatch(full)
if len(sub) >= 5 && sub[2] == sub[3] {
n, _ := strconv.ParseInt(sub[4], 10, 64)
bc := compileLenCmpBytecode("lt", n)
repls = append(repls, replacement{loc[0], loc[1], buildVMBoolFunc(sub[1], sub[2], bc)})
}
}
for _, loc := range vmLenGePattern.FindAllStringIndex(src, -1) {
full := src[loc[0]:loc[1]]
sub := vmLenGePattern.FindStringSubmatch(full)
if len(sub) >= 5 && sub[2] == sub[3] {
n, _ := strconv.ParseInt(sub[4], 10, 64)
bc := compileLenCmpBytecode("ge", n)
repls = append(repls, replacement{loc[0], loc[1], buildVMBoolFunc(sub[1], sub[2], bc)})
}
}
for _, loc := range vmArith2Pattern.FindAllStringIndex(src, -1) {
full := src[loc[0]:loc[1]]
sub := vmArith2Pattern.FindStringSubmatch(full)
if len(sub) >= 7 {
funcName, p1, p2, left, op, right := sub[1], sub[2], sub[3], sub[4], sub[5], sub[6]
if left == p1 && right == p2 {
bc := compileArith2Bytecode(op, 0, 1)
repls = append(repls, replacement{loc[0], loc[1], buildVMIntFunc2(funcName, p1, p2, bc)})
} else if left == p2 && right == p1 {
bc := compileArith2Bytecode(op, 1, 0)
repls = append(repls, replacement{loc[0], loc[1], buildVMIntFunc2(funcName, p1, p2, bc)})
}
}
}
for _, loc := range vmArith1Pattern.FindAllStringIndex(src, -1) {
full := src[loc[0]:loc[1]]
sub := vmArith1Pattern.FindStringSubmatch(full)
if len(sub) >= 6 {
funcName, param, left, op, nStr := sub[1], sub[2], sub[3], sub[4], sub[5]
if left == param {
n, _ := strconv.ParseInt(nStr, 10, 64)
bc := compileArith1Bytecode(op, n)
repls = append(repls, replacement{loc[0], loc[1], buildVMIntFunc1(funcName, param, bc)})
}
}
}
if len(repls) == 0 {
return src
}
for i := len(repls) - 1; i >= 0; i-- {
r := repls[i]
src = src[:r.start] + r.text + src[r.end:]
}
if !strings.Contains(src, "\"github.com/self-evolving-research/obfuscator/pkg/vm\"") {
src = t.ensureImport(src, "github.com/self-evolving-research/obfuscator/pkg/vm")
}
return src
}
func compileLenCmpBytecode(op string, n int64) []byte {
var bc []byte
bc = append(bc, vm.EncodeInstr(vm.OpPush, 0)...)
bc = append(bc, vm.EncodeInstr(vm.OpCall, 0)...)
bc = append(bc, vm.EncodeInstr(vm.OpPush, n)...)
switch op {
case "gt":
bc = append(bc, vm.EncodeInstr(vm.OpCmpGt, 0)...)
case "eq":
bc = append(bc, vm.EncodeInstr(vm.OpCmpEq, 0)...)
case "lt":
bc = append(bc, vm.EncodeInstr(vm.OpCmpLt, 0)...)
case "ge":
bc = append(bc, vm.EncodeInstr(vm.OpCmpLt, 0)...)
bc = append(bc, vm.EncodeInstr(vm.OpPush, 0)...)
bc = append(bc, vm.EncodeInstr(vm.OpCmpEq, 0)...)
}
bc = append(bc, vm.EncodeInstr(vm.OpHalt, 0)...)
return bc
}
func compileArith2Bytecode(op string, leftID, rightID int64) []byte {
var bc []byte
bc = append(bc, vm.EncodeInstr(vm.OpPush, 0)...) // arity=0
bc = append(bc, vm.EncodeInstr(vm.OpCall, leftID)...) // get left param
bc = append(bc, vm.EncodeInstr(vm.OpPush, 0)...) // arity=0
bc = append(bc, vm.EncodeInstr(vm.OpCall, rightID)...) // get right param
switch op {
case "+":
bc = append(bc, vm.EncodeInstr(vm.OpAdd, 0)...)
case "-":
bc = append(bc, vm.EncodeInstr(vm.OpSub, 0)...)
case "*":
bc = append(bc, vm.EncodeInstr(vm.OpMul, 0)...)
case "/":
bc = append(bc, vm.EncodeInstr(vm.OpDiv, 0)...)
case "%":
bc = append(bc, vm.EncodeInstr(vm.OpMod, 0)...)
case "^":
bc = append(bc, vm.EncodeInstr(vm.OpXor, 0)...)
case "&":
bc = append(bc, vm.EncodeInstr(vm.OpAnd, 0)...)
case "|":
bc = append(bc, vm.EncodeInstr(vm.OpOr, 0)...)
}
bc = append(bc, vm.EncodeInstr(vm.OpHalt, 0)...)
return bc
}
func compileArith1Bytecode(op string, n int64) []byte {
var bc []byte
bc = append(bc, vm.EncodeInstr(vm.OpPush, 0)...) // arity=0
bc = append(bc, vm.EncodeInstr(vm.OpCall, 0)...) // get param
bc = append(bc, vm.EncodeInstr(vm.OpPush, n)...) // constant
switch op {
case "+":
bc = append(bc, vm.EncodeInstr(vm.OpAdd, 0)...)
case "-":
bc = append(bc, vm.EncodeInstr(vm.OpSub, 0)...)
case "*":
bc = append(bc, vm.EncodeInstr(vm.OpMul, 0)...)
case "/":
bc = append(bc, vm.EncodeInstr(vm.OpDiv, 0)...)
case "%":
bc = append(bc, vm.EncodeInstr(vm.OpMod, 0)...)
case "^":
bc = append(bc, vm.EncodeInstr(vm.OpXor, 0)...)
case "&":
bc = append(bc, vm.EncodeInstr(vm.OpAnd, 0)...)
case "|":
bc = append(bc, vm.EncodeInstr(vm.OpOr, 0)...)
}
bc = append(bc, vm.EncodeInstr(vm.OpHalt, 0)...)
return bc
}
func buildVMBoolFunc(funcName, paramName string, bc []byte) string {
var byteLits []string
for _, b := range bc {
byteLits = append(byteLits, fmt.Sprintf("0x%02X", b))
}
return fmt.Sprintf(`//go:noinline
func %s(%s string) bool {
_co := map[int]func([]int64) int64{
0: func(_ []int64) int64 { return int64(len(%s)) },
}
_bc := []byte{%s}
_vm := vm.NewVM(_bc, _co)
return _vm.Run() != 0
}`, funcName, paramName, paramName, strings.Join(byteLits, ", "))
}
func buildVMIntFunc2(funcName, p1, p2 string, bc []byte) string {
var byteLits []string
for _, b := range bc {
byteLits = append(byteLits, fmt.Sprintf("0x%02X", b))
}
return fmt.Sprintf(`//go:noinline
func %s(%s, %s int64) int64 {
_co := map[int]func([]int64) int64{
0: func(_ []int64) int64 { return %s },
1: func(_ []int64) int64 { return %s },
}
_bc := []byte{%s}
_vm := vm.NewVM(_bc, _co)
return _vm.Run()
}`, funcName, p1, p2, p1, p2, strings.Join(byteLits, ", "))
}
func buildVMIntFunc1(funcName, param string, bc []byte) string {
var byteLits []string
for _, b := range bc {
byteLits = append(byteLits, fmt.Sprintf("0x%02X", b))
}
return fmt.Sprintf(`//go:noinline
func %s(%s int64) int64 {
_co := map[int]func([]int64) int64{
0: func(_ []int64) int64 { return %s },
}
_bc := []byte{%s}
_vm := vm.NewVM(_bc, _co)
return _vm.Run()
}`, funcName, param, param, strings.Join(byteLits, ", "))
}
func (t *Transformer) injectAntiDisassembly(src string) string {
lines := strings.Split(src, "\n")
var result []string
inFuncBody := false
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "func ") && strings.HasSuffix(trimmed, "{") {
inFuncBody = true
result = append(result, line)
continue
}
if inFuncBody && trimmed == "}" {
inFuncBody = false
result = append(result, line)
continue
}
if inFuncBody && strings.HasPrefix(line, "\t") && !strings.HasPrefix(line, "\t\t") && trimmed != "" {
if t.rng.Float64() < 0.15 {
junkVar := stringcrypt.RandomIdent("_ad", 4)
junkSize := t.rng.Intn(32) + 8
result = append(result, fmt.Sprintf("\tvar %s [%d]byte; _ = %s", junkVar, junkSize, junkVar))
}
}
result = append(result, line)
}
return strings.Join(result, "\n")
}
func (t *Transformer) injectJunkStrings(src string) string {
junkStrings := []string{
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"0123456789abcdef0123456789abcdef",
"/usr/lib/libSystem.B.dylib",
"/proc/self/maps",
"\\x00\\x00\\x00\\x00",
"HKEY_LOCAL_MACHINE\\SOFTWARE",
"C:\\Windows\\System32\\ntdll.dll",
"SELECT * FROM users WHERE id=?",
"Content-Type: application/json",
"Mozilla/5.0 (Windows NT 10.0)",
"-----BEGIN RSA PRIVATE KEY-----",
"192.168.1.1:8080",
"admin@localhost",
"Bearer eyJhbGciOiJIUzI1NiJ9",
"application/x-www-form-urlencoded",
}
numJunk := t.rng.Intn(6) + 4
var junkBlock strings.Builder
junkBlock.WriteString("\nvar _junkData = [...]string{\n")
for i := 0; i < numJunk; i++ {
idx := t.rng.Intn(len(junkStrings))
junkBlock.WriteString(fmt.Sprintf("\t%q,\n", junkStrings[idx]))
}
junkBlock.WriteString("}\n")
junkBlock.WriteString("var _ = _junkData\n")
insertAt := findImportBlockEnd(src)
if insertAt == -1 {
pkgIdx := strings.Index(src, "package ")
if pkgIdx != -1 {
lineEnd := strings.Index(src[pkgIdx:], "\n")
if lineEnd != -1 {
insertAt = pkgIdx + lineEnd + 1
}
}
}
if insertAt != -1 && insertAt < len(src) {
src = src[:insertAt] + junkBlock.String() + src[insertAt:]
}
return src
}
func (t *Transformer) ensureImport(src string, importPath string) string {
importBlock := `import ("` + importPath + `")`
if idx := strings.Index(src, "import ("); idx != -1 {
insertAt := idx + len("import (")
return src[:insertAt] + "\n\t\"" + importPath + "\"" + src[insertAt:]
}
if idx := strings.Index(src, "import \""); idx != -1 {
return src[:idx] + importBlock + "\n" + src[idx:]
}
pkgEnd := strings.Index(src, "\n\n")
if pkgEnd == -1 {
pkgEnd = strings.Index(src, "\n")
}
if pkgEnd != -1 {
return src[:pkgEnd+1] + "\n" + importBlock + "\n" + src[pkgEnd+1:]
}
return src
}