mirror of
https://github.com/lukaszraczylo/go-obfuscation.git
synced 2026-10-12 05:32:33 +00:00
1610 lines
40 KiB
Go
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
|
|
}
|